如何从Java(Android)中使用函数指针调用C++方法

3

我需要将C++文件集成到我的Android应用程序项目中。 我可以构建这些文件并生成.so文件。

这是一个包含函数process()的头文件。我需要从我的.java文件中调用这个方法。

class PayrollGenerator {

public:
    typedef void (* PAYROLL_READY_CALLBACK) (std::vector<int> list, int id);
    typedef void (* PROGRESS_CALLBACK) (int progress);

private:
    PAYROLL_READY_CALLBACK _dataReadyCallback;
    PROGRESS_CALLBACK _progressCallback;

public:
     DataProcessor(PAYROLL_READY_CALLBACK dataReadyCallback, PROGRESS_CALLBACK progressCallback);
    void process(int data);
};

有两个回调可以提供正在处理的数据的结果和进度数据。 我无法为此设计JNI方法。 顺便说一下,我从.java文件中运行简单的C++程序。 但这个程序对我来说相当复杂。 请帮忙!

进度 - 我创建了一个C++文件并编写了包装器。

JNIEnv *global;
jobject impl;

struct DataProcessorStruct {
jobject callback_ptr;
DataProcessor *dataProcessor;
};

void dataReadyCallback(std::vector<jint> processedSamples, jint heartRate){
jintArray arr = global->NewIntArray( processedSamples.size() );
global->SetIntArrayRegion( arr, 0, processedSamples.size(), ( jint * ) &processedSamples[0] );

jclass clazz = global->FindClass("com/app/AudioActivity");
jmethodID method = global->GetMethodID(clazz, "dataReady","[Ljava/util/List;I)V");
global->CallVoidMethod(impl,method,arr,heartRate);
__android_log_print(ANDROID_LOG_INFO, "test", "C-dataReadyCallback");
}

void progressCallback(jint progress){

jclass clazz = global->FindClass("com/app/AudioActivity");
jmethodID method = global->GetMethodID(clazz, "dataProcessProgress","(I)V");
global->CallVoidMethod(impl, method,progress);
__android_log_print(ANDROID_LOG_INFO, "test", "C-progressCallback");
}

 JNIEXPORT jlong JNICALL Java_com_app_AudioActivity_createDataProcessorObject (JNIEnv * env, jobject obj){
global = env;
impl = obj;

DataProcessorStruct *cpp_obj = new DataProcessorStruct();

if (cpp_obj == NULL) {
    return 0;
}

DataProcessor *csObj = new DataProcessor(dataReadyCallback,progressCallback);
if (csObj == NULL) {
    return 0;
}
cpp_obj->dataProcessor = csObj;
cpp_obj->callback_ptr = env->NewGlobalRef(obj);
return (jlong)cpp_obj;
}


JNIEXPORT void JNICALL Java_com_app_processData (JNIEnv * env, jobject obj,jlong objId,jint sample,jint dataLeft){
impl = obj;
DataProcessorStruct *cpp_obj = (DataProcessorStruct *)objId;
DataProcessor *dataProcessor=cpp_obj->dataProcessor;
if (dataProcessor != NULL){
    dataProcessor->process(sample,dataLeft);
}
}

Java 中的本地方法包括 -
public native long createDataProcessorObject();
public native void processData(long dataProcessor,int sample, int dataLeft);

这是正确的方法吗?另外,是否有任何方法可以让我在dataReadyCallback()和progressCallback()C++方法中不必直接调用Java类方法,而是可以调用位于Java中的接口方法,以便任何监听这些回调的类都应该得到通知,而不仅仅是特定类?


你需要在Java类型和C++类型之间进行数据转换。可以查看SWIG - Captain Obvlious
由于公司政策的限制,我无法使用第三方软件。 - Shadab Ansari
SWIG是一种工具,它生成代码以在不同编程语言之间进行调用,如果你的公司禁止你使用提高生产力的工具,那么你的公司很糟糕。 - Captain Obvlious
我尝试了SWIG,但是当我将我的C++代码放入其中时,它没有给出任何结果。 - Shadab Ansari
1个回答

0

您可以轻松地在Java中定义本地函数,这些函数是C++程序中的回调函数。通常,您会开始声明一个类和一些Java函数:

class MyJNative {
    static {   // static bloc, executed when class is loaded => load your cpp library
        System.out.print ("Load library in ");
        System.out.println(System.getProperty("java.library.path"));
        System.loadLibrary("MyNativeCPP");
    }

    private static int next_id=1;   // every object gets a unique id
    private int id;                 // unique object id

    public MyJNative () {           // constructor (sets unique id)
        id = next_id++; 
        // ... 
    }

    public native static void doSetup(); // initialisation for your factory function
    public native void doSomething();  // example method 
    ...
}

然后你可以让javah为C++中的本地函数生成头文件。你不应该手写头文件!这里有一个教程的逐步说明,以及上面代码的示例:

...
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     MyJNative
 * Method:    doSetup
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_MyJNative_doSetup
  (JNIEnv *, jclass);

/*
 * Class:     MyJNative
 * Method:    doSomething
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_MyJNative_doSomething
  (JNIEnv *, jobject);

...    
#ifdef __cplusplus
}
#endif
#endif

正如您所见,JNI旨在调用C++/C函数而不是C++类的成员函数。因此,您需要定义一些全局函数(或您的类的静态成员函数),它们将作为(第一个)参数获取可以标识C++对象的ID,并将调用转发到该对象。

上面的示例是简化的,但思想是,Java类维护唯一的ID。您可以想象,在创建后设置ID后,它会调用C++中的工厂函数,该函数将维护一个map<jint, unique_ptr<MyCPPclass>> objects,以便所有其他函数(如doSomething())都可以通过ID作为参数调用,然后调用objects[ID].get()->doSomething()

这种方法的问题在于C++对象的生命周期:您管理其销毁:在C++端创建的C++对象不会被垃圾回收,因此很难猜测它们何时不再需要。

一种此方法的变体是将其“托管”到Java对象的字节数组中的某个地方。但在这种情况下,问题刚好相反:当不再需要Java对象时,C ++端就不会被通知对象已被销毁,因此析构函数也不会被调用。
正如您所看到的,JNI设计使得在两侧管理对象相当困难。因此,最安全的方法是仅在一侧管理对象,在另一侧暴露仅使用/操作对象的函数。

你能否提供一些解决该问题的代码? - Shadab Ansari
我知道这个,但我的问题不同。我想调用的方法包含回调作为参数,这就是我被阻塞的地方。 - Shadab Ansari
这正是相同的原理:回调指针是C++类数据。您需要一个本地包装函数,找到/构造您的类并调用成员函数指针。 - Christophe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接