从C++回调到Java的回调函数

6
我已经在C++中编写了以下代码用于数值积分:
// integrate.h:
#ifdef BUILDING_DLL
#define  DLL_MACRO __declspec(dllexport)
#else
#define  DLL_MACRO __declspec(dllimport)
#endif

extern "C" {
  typedef double (*Function1VariablePtr)(double x);
  double DLL_MACRO integrate(Function1VariablePtr function, double min, double max);
}

// integrate.cpp: 
#include "integrate.h"
double integrate(Function1VariablePtr function, double min, double max) {
  const int n = 1001;
  double dx  = (max - min)/(1.0*(n - 1));
  double sum = 0.0;
  for(int i = 0; i < n; i++) {
    double xmid = min + (i + 0.5)*dx;
    sum += function(xmid)*dx;
  }
  return sum;
}

现在我想从Java中调用这个函数。我找到了如何直接在JNI“桥”代码中实现集成:
// C++ "bridge" code to from/to Java:  
JNIEXPORT jdouble JNICALL 
Java_IntegrateJApp_JIntegrate(JNIEnv *jnienv, jclass jc,    
                              jdouble xmin, jdouble xmax) {
  jmethodID mid = jnienv->GetStaticMethodID(jc, "Function1D","(D)D");
  if (mid == 0)
    return - 1.0;
  const int n = 1001;
  double dx  = (xmax - xmin)/(1.0*(n - 1));
  double sum = 0.0;
  for(int i = 0; i < n; i++) {
    double xmid = xmin + (i + 0.5)*dx;
    double f = jnienv->CallStaticDoubleMethod(jc, mid, xmid);
    sum += f*dx;
  }
  return sum;
}

// Java code calling "bridge":
class IntegrateJApp { 
  public static void main(String[] args) { 
      System.loadLibrary("JIntegrate");
      double I = JIntegrate(0.0, 2*Math.PI);
      System.out.println( Double.toString(I) ); 
  } 
  public static double Function1D(double x) {
      return Math.sin(x);
  }
  public static native double JIntegrate(double xmin, double xmax);
} 

然而,我不想在C++桥接代码中直接实现数值积分,而是调用integrate.cpp中的代码。

我该如何做? integrate.cpp中的integrate()函数需要一个函数指针,而我没有。 有没有办法使用JNI在Java中获取函数指针?

感谢您提前的任何答案!


你实际上是在问如何将你的C++ JNI包装器与现有库链接。 - Nick
是的,没错。问题在于现有库中的函数需要一个函数指针。我该如何在Java中获取函数指针? - Andy
2个回答

3

您需要使用C++代码创建一个DLL,并从JNI中调用它。

加载DLL:

System.loadLibrary("PATH\\yourdllname.dll");

创建函数链接

public static native integrate(parameters);

是的,我可以从integrate.cpp创建一个dll:integrate.dll。接下来,我可以从“bridge”调用此dll:JNIintegrate.dll。但是,我的integrate.cpp中的integrate函数需要一个函数指针。我如何在“bridge”代码中获取一个函数指针,以便将其传递给integrate()? - Andy
path\\yourdllname.dll?你一定是指 path/yourlibname.so!有趣的是,Java跨平台能力如此强大...我想JNI是我可以原谅他们犯这个错误的唯一地方。 - asveikau
这只是一个例子,你可以使用.dll、.so或什么都不用,但为了更好地理解,我使用了它,这有问题吗? :P - RamonBoza
无关回答 -- 该人在询问如何从C++ JNI接口调用静态库。 - Nick
1
为什么人们喜欢这个答案,问题实际上只是关于C++(而不是Java)。 - bestsss

2

一种可以实现的方法是使用成员函数指针并改变integrate函数的签名。

具体思路如下:

functionwrapper.h

声明一个函数包装器类。

class FunctionWrapper
{
public:
    typedef double (FunctionWrapper::*Function1VariablePtr)(double x);

    FunctionWrapper(JNIEnv*, jclass);
    double compute(double x);
};

integrate.h

消除以前的函数指针 typedef,并改变方法签名,使其包含包装器对象和指向其成员函数的指针。

#include "functionwrapper.h"
extern "C" {
    double DLL_MACRO integrate(FunctionWrapper*, FunctionWrapper::Function1VariablePtr, double min, double max);
}

integrate.cpp

将函数调用更改为成员函数调用。

#include "integrate.h"
double integrate(FunctionWrapper* wrapper, FunctionWrapper::Function1VariablePtr function, double min, double max)
{
    // ...
    sum += (wrapper->*function)(xmid)*dx;
    // ...
}

返回总和;}

JNI "桥接" 代码:

定义包装器代码并定义执行实际调用的函数。直接从JNI函数调用integrate函数:

#include "functionwrapper.h"

FunctionWrapper::FunctionWrapper(JNIEnv *jnienv, jclass jc) : m_jnienv(jnienv), m_jc(jc) {
    m_method= jnienv->GetStaticMethodID(jc, "Function1D","(D)D");
}

double FunctionWrapper:compute(double x) {
    return m_jnienv->CallStaticDoubleMethod(m_jc, m_method, x);;
}

// C++ "bridge" code to from/to Java:
JNIEXPORT jdouble JNICALL
Java_IntegrateJApp_JIntegrate(JNIEnv *jnienv, jclass jc,
                              jdouble xmin, jdouble xmax) {
    FunctionWrapper wrapper(jnienv, jc);
    return integrate(&wrapper, &FunctionWrapper::compute, 2, 3);
}

1
谢谢Nick!然而在我看来你好像声明了两次FunctionWrapper?我尝试使用一个抽象基类:IFunctionWrapper和它的JNI实现:JFunctionWrapper。然后我调用了integrate(jfw,(IFunctionWrapper :: Function1VariablePtr) &JFunctionWrapper :: compute,xmin,xmax); 这种方法也有效。 - Andy
是的,你说得对——这个类被声明了两次——我会修复它(文件太多了)。 - Nick
(wrapper->*function)(xmid) 这个调用让我非常惊讶。我不知道这是可能的。实际上,你在一个对象上调用了一个成员函数指针? - Andy
没错!你基本上是将成员函数和应用它的对象传递进去。 - Nick

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