从 C++ 库中嵌入 Java 代码而不需要***程序来嵌入它?

Posted

技术标签:

【中文标题】从 C++ 库中嵌入 Java 代码而不需要***程序来嵌入它?【英文标题】:Embed java code from a C++ library without needing top-level program to embed it? 【发布时间】:2015-03-31 14:48:05 【问题描述】:

我正在使用 QtCreator 在android 上部署 C++/Java 应用程序。但我认为我的问题可能并不特定于 QtCreator 部署应用程序的方式。

我想创建一个提供特定功能的 C++ 库。为此,该库需要实例化一个 Java 类,最后一个将用于执行一些 SDK 函数类(用于 NDK/C++ API 中不可用的东西)。

从 C++ 程序创建和使用 java 对象可以正常工作。我在编译/部署期间将 .java 文件打包到应用程序环境中,然后我可以通过两种方法使用 Java 类:

声明JNI_OnLoad,加载类id,方法id,稍后使用jni调用它们 使用 Qt QAndroidJniObject 对象(这是 QtCreator 特有的)

现在,当我想从 C++ 库中创建和使用 java 对象时,问题就来了。仅当 .java 文件与***应用程序一起打包时才有效。我找不到一种方法将 java 与库本身打包,并且只能与库本身一起打包。这意味着任何需要使用我的库的人不仅需要简单地与库链接,还需要打包我的库所需的 .java 文件。这打破了封装,给最终开发人员编写程序并只想加载一个库并期望它嵌入它自己工作所需的所有内容带来了困难......

我的问题是:库如何嵌入java文件,这样这个java文件就不需要成为顶层程序包的一部分来让库使用它?

这里是一个简单的示例: MainWindow 构造函数调用 4 个函数自己尝试创建和使用 Java 对象。只有前两个调用有效...

main.cpp:

#include <QApplication>
#include <QMainWindow>

#include "MyLib.h"

#include <QtAndroidExtras/QAndroidJniObject>
#include "jni.h"

#include <assert.h>

// load java classes from main program

JavaVM* s_javaVM = NULL;
jclass s_classID = 0;
jmethodID s_ctorMethodID = 0;
jmethodID s_callmethodID = 0;

bool loadJava( JNIEnv *env )

    jclass clazz = env->FindClass("my/FooPrg");
    if (!clazz)
    
        qCritical("Can't find FooPrg class");
        return false;
    
    // keep a global reference to it
    s_classID = (jclass)env->NewGlobalRef(clazz);

    // search for its contructor
    s_ctorMethodID = env->GetMethodID(s_classID, "<init>", "()V");
    if (!s_ctorMethodID )
    
        qCritical("Can't find class contructor");
        return false;
    

    // search for a method
    s_callmethodID = env->GetMethodID(s_classID, "Mult", "(I)I");
    if (!s_callmethodID )
    
        qCritical("Can't find Mult method");
        return false;
    

    return true;


jint JNICALL JNI_OnLoad(JavaVM *vm, void *)

    s_javaVM = vm;

    JNIEnv* env = NULL;
    if (s_javaVM->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_4) != JNI_OK)
        return -1;

    loadJava( env );

    return JNI_VERSION_1_4;


void callJavaFunctionFromPrgWithQt()

    if ( QAndroidJniObject::isClassAvailable("my/FooPrg") )
    
        QAndroidJniObject obj("my/FooPrg","()V");
        if ( obj.isValid() )
        
            jint res = obj.callMethod<jint>("Mult", "(I)I", 0x0002);
            assert( res == 4 );
        
        else
        
            assert( false );
        
    
    else
    
        assert( false );
    


void callJavaFunctionFromPrgWithJniLoad()

    if ( s_classID != 0 && s_ctorMethodID != 0 && s_callmethodID != 0 )
    
        JNIEnv* env = NULL;
        if (s_javaVM->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_4) != JNI_OK)
            assert(false);

        jobject j_object = env->NewGlobalRef( env->NewObject(s_classID, s_ctorMethodID ) );
        jint res = env->CallIntMethod(j_object, s_callmethodID, 0x0002 );
        assert( res == 4 );
    
    else
    
        assert( false );
    


class MainWindow : public QMainWindow

public:
    MainWindow()
    
        callJavaFunctionFromPrgWithQt();        // works
        callJavaFunctionFromPrgWithJniLoad();   // works
        callJavaFunctionFromLibWithQt();        // fails, assert
        callJavaFunctionFromLibWithJniLoad();   // fails, because libraries JNI_OnLoad can't find FooLib.java!
    
;

int main(int argc, char *argv[])

    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();

MyLib.h:

#pragma once

void callJavaFunctionFromLibWithQt();
void callJavaFunctionFromLibWithJniLoad();

MyLib.cpp:

#include "MyLib.h"

#include <QtAndroidExtras/QAndroidJniObject>
#include "jni.h"

#include <assert.h>

// load java classes from main program

JavaVM* s_javaVM = NULL;
jclass s_classID = 0;
jmethodID s_ctorMethodID = 0;
jmethodID s_callmethodID = 0;

bool loadJava( JNIEnv *env )

    jclass clazz = env->FindClass("my/FooLib");
    if (!clazz)
    
        qDebug("Can't find FooLib class");
        return false;
    
    // keep a global reference to it
    s_classID = (jclass)env->NewGlobalRef(clazz);

    // search for its contructor
    s_ctorMethodID = env->GetMethodID(s_classID, "<init>", "()V");
    if (!s_ctorMethodID )
    
        qDebug("Can't find class contructor");
        return false;
    

    // search for a method
    s_callmethodID = env->GetMethodID(s_classID, "Mult", "(I)I");
    if (!s_callmethodID )
    
        qDebug("Can't find Mult method");
        return false;
    

    return true;


jint JNICALL JNI_OnLoad(JavaVM *vm, void *)

    s_javaVM = vm;

    JNIEnv* env = NULL;
    if (s_javaVM->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_4) != JNI_OK)
        return -1;

    // uncommenting this makes the application crash upon load....
    //loadJava( env );

    return JNI_VERSION_1_4;


void callJavaFunctionFromLibWithQt()

    if ( QAndroidJniObject::isClassAvailable("my/FooLib") )
    
        QAndroidJniObject obj("my/FooLib","()V");
        if ( obj.isValid() )
        
            jint res = obj.callMethod<jint>("Mult", "(I)I", 0x0002);
            assert( res == 4 );
        
        else
        
            assert( false );
        
    
    else
    
        assert( false ); // this assertion is reached!
    


void callJavaFunctionFromLibWithJniLoad()

    if ( s_classID != 0 && s_ctorMethodID != 0 && s_callmethodID != 0 )
    
        JNIEnv* env = NULL;
        if (s_javaVM->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_4) != JNI_OK)
            assert(false);

        jobject j_object = env->NewGlobalRef( env->NewObject(s_classID, s_ctorMethodID ) );
        jint res = env->CallIntMethod(j_object, s_callmethodID, 0x0002 );
        assert( res == 4 );
    
    else
    
        assert( false ); // this assertion is reached!
    

FooPrg.java:

package my;

import java.lang.Integer;

public class FooPrg

    public FooPrg()
    
    

    public int Mult(int val)
    
        return val * 2;
    

FooLib.java:

package my;

import java.lang.Integer;

public class FooLib

    public FooLib()
    
    

    public int Mult(int val)
    
        return val * 2;
    

jniload.pro:

TARGET = jniload

CONFIG += qt resources

QT += core gui widgets

android: QT += androidextras

SOURCES += src/main.cpp

TEMPLATE = app

INCLUDEPATH += ifc

LIBS +=  \ 
-l$$OUT_PWD/../../lib/jniload_lib/libjniload_lib.so

ANDROID_EXTRA_LIBS +=  \ 
$$OUT_PWD/../../lib/jniload_lib/libjniload_lib.so

ANDROID_PACKAGE_SOURCE_DIR = data/android/root

OTHER_FILES += data/android/root/src/my/FooPrg.java

jniload_lib.pro:

TARGET = jniload_lib

CONFIG += qt resources

QT += core gui widgets

android: QT += androidextras

SOURCES += src/MyLib.cpp

HEADERS += ifc/MyLib.h

TEMPLATE = lib

INCLUDEPATH += ifc

# This does has apparently no effect on library
ANDROID_PACKAGE_SOURCE_DIR = data/android/root

OTHER_FILES += data/android/root/src/my/FooLib.java

【问题讨论】:

您是否熟悉 Qt 资源系统,该系统将外部文件编译成二进制文件以从 Qt 方法中使用?我对你正在做的事情一无所知,但这听起来像是一个可能的想法。注意:我从未将 Qt 资源系统用于库,仅用于应用程序。 我经常将 Qt 资源用于库和应用程序。但我不认为在这种特殊情况下存储 .java 文件会有所帮助。除非有办法告诉 Java 去这里寻找资源...... 【参考方案1】:

终于有办法解决这个问题了。

我从 jniload.pro 文件中删除了 ANDROID_PACKAGE_SOURCE_DIR 行,并通过自定义构建步骤手动复制了 .java 文件:

custom_jniload_lib_step.target = jniload_lib_mockup.h
custom_jniload_lib_step.commands = $(COPY_DIR) data\android\root ..\..\android-build
QMAKE_EXTRA_TARGETS += custom_jniload_lib_step
PRE_TARGETDEPS += jniload_lib_mockup.h

custom_jniload_step.target = jniload_mockup.h
custom_jniload_step.commands = $(COPY_DIR) data\android\root ..\..\android-build
QMAKE_EXTRA_TARGETS += custom_jniload_step
PRE_TARGETDEPS += jniload_mockup.h

然后,在部署时,android-build/src 包含 FooLib.java 和 FooPrg.java,然后库和程序都可以访问它们!

【讨论】:

以上是关于从 C++ 库中嵌入 Java 代码而不需要***程序来嵌入它?的主要内容,如果未能解决你的问题,请参考以下文章

使用 CMake 将资源(例如,着色器代码;图像)嵌入到可执行文件/库中

将图像设置为库中的图像视图,而不从图像库中选择图像

如何从 git 存储库中删除文件而不实际删除文件 [重复]

从 C# 调用 C++ 代码而不创建 dll?

在java中运行pig而不嵌入pig脚本

无法从 JNI 设置 Java int 数组字段