简体   繁体   English

从C ++库嵌入Java代码,而无需顶层程序嵌入它?

[英]Embed java code from a C++ library without needing top-level program to embed it?

I'm using QtCreator to deploy C++/Java applications on Android . 我正在使用QtCreator在Android上部署C ++ / Java应用程序。 But I think my problem may not be specific to the way QtCreator deploys the app. 但是我认为我的问题可能并不特定于QtCreator部署应用程序的方式。

I want to create a C++ library providing a specific functionnality. 我想创建一个提供特定功能的C ++库。 To do so, the library needs to instantiate a Java class, this last one will be used to do some SDK functions class (for stuff that are not available in the NDK/C++ API). 为此,该库需要实例化一个Java类,这最后一个将用于执行一些SDK函数类(用于NDK / C ++ API中不可用的东西)。

Creating and using java objects from a C++ program works fine. 从C ++程序创建和使用Java对象可以正常工作。 I package the .java file to the application environment during compilation/deployment and then I can use the Java class via two approachs: 我在编译/部署期间将.java文件打包到应用程序环境中,然后可以通过两种方法使用Java类:

  • Declare JNI_OnLoad , load class id, method id, and later call them using jni 声明JNI_OnLoad ,加载类ID,方法ID,然后使用jni对其进行调用
  • Use Qt QAndroidJniObject objects (this is specific to QtCreator) 使用Qt QAndroidJniObject对象(特定于QtCreator)

Now the problem comes when I want to create and use java objects from a C++ library. 现在,当我想从C ++库创建和使用Java对象时,问题就来了。 It only works if the .java file is packaged with the top-level application. 仅当.java文件与顶级应用程序打包在一起时,它才有效。 I could not find a way to package the java with and only with the library itself. 我无法找到仅与库本身一起打包Java的方法。 Meaning that anyone why needs to use my library will not only have to simply link with the library, but will also need to package the .java file(s) needed by my library. 这意味着任何需要使用我的库的人不仅必须简单地与库链接,而且还需要打包我的库所需的.java文件。 This breaks encapsulation and gives a hard time to the end developer writing programs and simply wanting to load a library and expecting it to embed all it needs to work on its own... 这会破坏封装,并给最终开发人员编写程序带来困难,他们只是想加载一个库并期望它能够嵌入自己需要工作的所有内容...

My question is: How can the library embed the java file, so that this java file does not need to be part of the top level program package to let the library use it? 我的问题是:该库如何嵌入Java文件,以便该Java文件不需要成为顶层程序包的一部分即可让该库使用它?

Here is a quick sample: MainWindow constrctor calls 4 functions themselves trying to create and use Java objects. 这是一个快速示例: MainWindow构造函数自己调用4个函数来尝试创建和使用Java对象。 Only the first two calls work... 只有前两个电话有效...

在此处输入图片说明

main.cpp: 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: MyLib.h:

#pragma once

void callJavaFunctionFromLibWithQt();
void callJavaFunctionFromLibWithJniLoad();

MyLib.cpp: 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: FooPrg.java:

package my;

import java.lang.Integer;

public class FooPrg
{
    public FooPrg()
    {
    }

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

FooLib.java: FooLib.java:

package my;

import java.lang.Integer;

public class FooLib
{
    public FooLib()
    {
    }

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

jniload.pro: 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: 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

Finaly got a way to work this out. Finaly有办法解决这个问题。

I removed ANDROID_PACKAGE_SOURCE_DIR line from jniload.pro file and hanlde manual copy of the .java files through custom build steps: 我通过自定义构建步骤从jniload.pro文件中删除了ANDROID_PACKAGE_SOURCE_DIR行,并手动创建了.java文件的hanlde手动副本:

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

Then, upon deployment, android-build/src contains both FooLib.java and FooPrg.java and then both library and program can access them! 然后,在部署时,android-build / src同时包含FooLib.java和FooPrg.java,然后库和程序都可以访问它们!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM