在Jni中,提供的最直接的调用c/c++的方式是函数。但是在很多时候,我们拿到手的并不一定是纯c的代码。而且,面向对象的程序结构方式对于模块化和程序的解耦和有着非常积极的作用。用一个c++的类与Java类相对应,可以思路更清晰的建立连接。
OK,废话不多说了,下面就来讲解具体的实现方法:

1.用地址抓住c++对象的把柄

假设我们要调用的c++对象定义如下:

class Person{
private:
    int age;
    const char* name;
public:
    Person();
    void init(int,const char*);
    void say_info();
    void writeFile(const char* path_name,const char* content);
};

首先,我们不仅要想要产生对象,而且要在需要的时候能够找到这个对象,这就需要在java中与c++中建立一点联系,这个联系通过传址来实现。也就是说,如果我们可以在java中持有一个C++对象,首先要设法调用该对象的构造函数,开辟一块内存,产生一个对象,然后再把这个对象存在的地址记录到java对象里面,这样下次就可以通过这个地址来找到c++的对象了。
现在问题来了,到底咋存呢?
答案是用一个long型就够了,其实就是拿它作指针用。
然后我们要有一个函数来创建本地对象并且返回它的地址,所以这个类目前看上去像这样:

public class JniPerson {
    //保存c++类的地址
    long nativePerson;
    //构造函数
    public JniPerson(){
        nativePerson = createNativeObject();
    }
    /**本地方法:创建c++对象并返回地址*/
    private native long createNativeObject();
}

为了防止忘掉创建这个对象,我把它的创建写在了构造函数里。
然后就是如何获得这个地址,只需要在映射本地方法的函数中这么写:

JNIEXPORT jlong JNICALL Java_ouc_sei_test_JniPerson_createNativeObject
(JNIEnv * env, jobject obj){
    jlong result;
    result =(jlong) new Person();
    return result;
}//为了方便观察我把这个过程拆了,实际上这里一句就够了

从这里我们可以看出,C++中对象的构造函数并不是没有返回值,而是省略了不写而已,任何c++对象的构造函数的返回值实际上是这个对象的指针。然后我们使用强制转换把这个指针转换为jlong然后返回给了java对象中的nativePerson来保存这个对象的地址。

2调用对象的方法

有了这个对象的地址,我们就可以在java中很方便的调用该对象的方法了。例如我们要调用该对象的init(int,const char*);方法,这个方法的作用是给私有属性赋值。java class里的本地方法这么写:

private native void nativeInitPerson(long personAddr,int age,String name);

请留意一下第一个参数,这就是对象的地址。然后我们会利用它来传入两个变量(在java中这种风格的的函数叫做代理)。
调用时的写法:

JNIEXPORT void JNICALL Java_ouc_sei_test_JniPerson_nativeInitPerson
  (JNIEnv * env, jobject obj, jlong thiz, jint jage, jstring jname){
    const char* name_str = env->GetStringUTFChars(jname,0);
    //对象指针调用方法
    ((Person*)thiz)->init(jage,name_str);
}

name是个String型,所以需要转换一下,而age可以直接与cpp对接。
加注释下面的一句是重点,thiz是个jlong型,其实就是我们传入的对象地址。我们强制把它转换回了指向该对象的指针,并且调用该对象的方法,这样就实现了对c++类方法的调用。

值得一提的是,在opencv的java代码中广泛使用了这种方式来调用c++的类库。