【问题标题】:How to invoke C++ methods with function pointers as arguments from Java (Android)如何从 Java (Android) 调用函数指针作为参数的 C++ 方法
【发布时间】:2015-08-24 21:54:51
【问题描述】:

我需要将 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 中的接口方法来从这些 C++ 方法调用,以便任何类监听这些回调应该得到通知,而不是特定的类?

【问题讨论】:

  • 您需要在 Java 类型和 C++ 类型之间编组数据。查看SWIG
  • 由于公司政策,我不能使用第三方软件。
  • SWIG 是一种生成代码以编组语言之间调用的工具,如果您的公司禁止您使用让您高效的工具,那么您的公司很糟糕。
  • 我尝试了 SWIG,但是当我将 C++ 代码发布到其中时,它没有给出任何结果。

标签: android c++ android-studio java-native-interface


【解决方案1】:

您可以轻松地在 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++ 中的本机函数生成标头。你不应该自己手写标题!这里一步一步tutorial,以及上面代码的例子:

...
#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&lt;jint, unique_ptr&lt;MyCPPclass&gt;&gt; objects,以便可以使用 ID 作为参数调用所有其他函数(如 doSomething()),然后调用 objects[ID].get()-&gt;doSomething()

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

这种方法的一种变体是将其“托管”在 java 对象的字节数组中的某个位置。但在这种情况下,问题是相反的:当不再需要 Java 对象时,C++ 端不会被告知该对象已被破坏,因此不会调用析构函数。

如您所见,JNI 设计使得管理双方的对象相当困难。因此,最安全的方法是仅在一侧管理对象,而在另一侧仅公开将使用/操作对象的函数。

【讨论】:

  • 你能提供一些代码来解决上述问题吗?
  • 我知道这一点,但我的问题不同。我要调用的方法包含回调作为参数,这就是我被阻止的地方。
  • 原理完全一样:回调指针都是c++类数据。您需要一个本地包装函数来查找/构造您的 claa 并调用您的成员函数指针。
猜你喜欢
  • 1970-01-01
  • 2013-03-31
  • 1970-01-01
  • 1970-01-01
  • 2011-12-14
  • 2017-11-28
  • 1970-01-01
  • 2021-12-10
  • 1970-01-01
相关资源
最近更新 更多