繁体   English   中英

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

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

我正在使用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

Finaly有办法解决这个问题。

我通过自定义构建步骤从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

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

暂无
暂无

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

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