实现一个简单的JNI的小demo

Jni在idea上的调试

安装MinGW64位

安装MinGW的方法很多,可以前往

https://sourceforge.net/projects/mingw/files/MinGW/Base/gcc/ 中自己选择需要的包。老实说,我也不是很清楚哪些包需要下载,没花时间研究,感兴趣的自己研究一些各个包的功能。不过这里有个安装教程

https://github.com/cpluspluscom/ChessPlusPlus/wiki/MinGW-Build-Tutorial按照这里的方法安装就可以。

另外,针对64位的,这里https://nuwen.net/mingw.html提供了完整的压缩包,直接下载https://nuwen.net/files/mingw/mingw-14.1.exe运行。

百度云

链接: https://pan.baidu.com/s/1slpQrrJ
密码: fykw

解压完成后,刚才指定的解压目录中的bin加入到path环境变量中。例如上面图中解压到E:\MinGw,那么应当将E:\MinGw\bin加入到环境变量path中。注意,请确保bin目录确实在E:\MinGw中,如果不在,可能在更深一层目录中,自行确定bin的目录。

做完后,打开控制台,输入:gcc -v 检验。

 

关于头文件的生成

使用IDEA编译java文件,并生成.h头文件。一共有两种方法:手动和IDEA一键生成。

手动输入javah指令:

JNI生成头文件是通过JDK中提供的javah来完成,javah在 {JDKHome}/bin目录中。用法如下:

javah -jni -classpath (搜寻类目录) -d (输出目录) (类名)

 

例如,将E:\Porject\out\com\huachao\java目录中的HelloJNI.class生成头文件,并放入到E:\Project\jni中:

javah -jni -classpath  E:\Porject\out\com\huachao\java -d

E:\Project\jni  com.huachao.java.HelloJNI.java

需要注意的是,使用javah来生成头文件(.h)时,-classpath指定的是编译后的java文件(.class)的目录,而不是源文件(.java)的目录,因此在使用javah指令之前,先build一下项目(或直接运行一下)。此时会生称out目录,所有编译后的文件都会存放在这个目录中。

实现一个简单的JNI的小demo

 

接下来,直接在IDEA的Terminal窗口运行javah:

javah -classpath  out\production\untitled -d ./jni  com.jyf.Hello.java

实现一个简单的JNI的小demo

实现一个简单的JNI的小demo

 

 

接下来我们只需实现JNICALL Java_com_jyf_Hello_sayHello(JNIEnv *, jobject)即可。仔细观察就会发现这个函数名称是有规律的,即Java_<包>_<类名>_<函数名>;

一键生成头文件

Idea工具下,只需配置如下:

File>Settings>Tools>External Tools:

添加一个先的External Tools:

实现一个简单的JNI的小demo

 

程序路径:$JDKpath$/bin/javah

参数:-jni -classpath $OutputPath$-d ./jni $FileClass$

项目名称:$ProjectFileDir$($ModuleFileDir$)

实现一个简单的JNI的小demo

 

 

C文件并编译成dll(或so)文件

手动输入命令生成

gcc -c -I"C:\Program Files\Java\jdk1.8.0_121\include" -I"C:\Program Files\Java\jdk1.8.0_121\include\win32"  jni/HelloImpl.c

生成的.o文件在项目的根目录下:

实现一个简单的JNI的小demo

 

然后将.o文件生成.dll文件

gcc -Wl,--add-stdcall-alias -shared -o HelloImpl.dll HelloImpl.o

实现一个简单的JNI的小demo

 

也可以通过一个命令 将.c文件直接转换成.dll文件

gcc -Wl,--add-stdcall-alias -I"C:\Program Files\Java\jdk1.8.0_121\include" -I"C:\Program Files\Java\jdk1.8.0_121\include\win32" -shared -o ./lib/hello.dll ./jni/HelloImpl.c

 

创建lib文件夹,将c文件编译成了dll,在这里把生成的dll文件加入到了lib目录中,而不是像之前那直接放到项目底下。因此在java.library.path应该指定目录为lib。

实现一个简单的JNI的小demo

 

路径结构类似于-Djava.library.path=F:/pro/znny_syx_standard/lib   

其中-Djava.library.path=为固定写法

 

一键生成dll

有了上面的命令后,可以很轻松的加入到External Tools中了。按照前面的方法,点击File>Settings>Tools>External Tools>+,输入内容如下:

name:Generate DLL

Program:<GCC路径>

Parameters:-Wl,--add-stdcall-alias -I"$JDKPath$\include" -I"$JDKPath$\include\win32" -shared -o ./lib/$FileNameWithoutExtension$.dll ./jni/$FileNameWithoutExtension$.c

Working Directory:$ProjectFileDir$($ModuleFileDir$)

实现一个简单的JNI的小demo

 

最后让java.library.path应该指定目录为lib

运行

实现一个简单的JNI的小demo

 

 

JNI——结构体(即java中的类)的传入与设置——NativeMethod映射表

 

参考:http://www.cnblogs.com/skywang12345/archive/2013/05/26/3093593.html

  http://www.2cto.com/kf/201501/372701.html

 

创建2个实体类MyInfo和Person

创建Java层核心类(处理俩实体类的业务逻辑,jni 接口)

其中:1、定义本地方法(要实现的业务逻辑)

实现一个简单的JNI的小demo

 

2、定义要加载的库()

实现一个简单的JNI的小demo

 

3、实现JNI

实现一个简单的JNI的小demo

 

至此,所有java文件写完,可以build工程项目,产生.Class文件。再运行插件,实现Javah命令,产生.h文件于jni文件中。

在jni文件中编写.c(.cpp)文件并修改.h文件。

修改.h文件:

 

  1. 1 加载所需的本地类

实现一个简单的JNI的小demo

 

  1. 2 绑定需要注册的类

实现一个简单的JNI的小demo

 

  1. 3 定义jfieldID结构体,用于保存类实体类的filedID

实现一个简单的JNI的小demo

 

  1. 4 与实体类对应的结构体,用于保存数据,并将数据赋值给实体类

实现一个简单的JNI的小demo

 

1.5 .h文件如果要存储值,需要定义数组空间

 

实现一个简单的JNI的小demo

 

实现一个简单的JNI的小demo

实现一个简单的JNI的小demo

 

编写.C文件:

如果是C数据传给JAVA,此时有几个必备的步骤:

1、Java和JNI函数的绑定表

2、注册native方法到java中

3、调用注册方法

4、JNI_OnLoad在jni注册时,会被回调执行。

5、可以定义个static方法:初始化函数,用于获取Java中各个成员对应的fieldID。

*前4个方法是C数据传JAVA 必配,获取fieldID可以写在各个实现方法内。

 

// 初始化函数,用于获取Java中各个成员对应的fieldID。
static void nativeClassInit (JNIEnv *env)
{
    jclass personClass = (*env)->FindClass(env, JNIPAR_CLASS);
    jclass myinfoClass = (*env)->FindClass(env, JNIPAR_CLASS_MYINFO);

    // 获取Person的mName成员对应的FieldID,并保存到gPersonOffsets中
    gPersonOffsets.name     = (*env)->GetFieldID(env, personClass, "mName"  , "Ljava/lang/String;");
    gMyInfoOffsets.name     = (*env)->GetFieldID(env, myinfoClass, "mName"  , "Ljava/lang/String;");

    // 获取Person的mAge成员对应的FieldID,并保存到gPersonOffsets中
    gPersonOffsets.age      = (*env)->GetFieldID(env, personClass, "mAge"   , "I");
    gMyInfoOffsets.age      = (*env)->GetFieldID(env, myinfoClass, "mAge"   , "I");

    // 获取Person的mHeight成员对应的FieldID,并保存到gPersonOffsets中
    gPersonOffsets.height   = (*env)->GetFieldID(env, personClass, "mHeight", "F");
    gMyInfoOffsets.height   = (*env)->GetFieldID(env, myinfoClass, "mHeight", "F");

}


// Java和JNI函数的绑定表
static JNINativeMethod method_table[] = {
    { "getPersonInfoByIndex", "(Lcom/jyf/Person;I)I", (void*)getPersonInfoByIndex },//绑定
    { "getMyInfoByIndex", "(Lcom/jyf/MyInfo;I)I", (void*)getMyInfoByIndex },//绑定

};

// 注册native方法到java中
static int registerNativeMethods(JNIEnv* env, const char* className,
        JNINativeMethod* gMethods, int numMethods)
{
    jclass clazz;
    clazz = (*env)->FindClass(env, className);
    if (clazz == NULL) {
        return JNI_FALSE;
    }
    if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
        return JNI_FALSE;
    }

    return JNI_TRUE;
}

int register_ndk_param(JNIEnv *env)
{
    nativeClassInit(env);
    // 调用注册方法
    return registerNativeMethods(env, JNIREG_CLASS,
            method_table, NELEM(method_table));
}

// JNI_OnLoad在jni注册时,会被回调执行。
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    JNIEnv* env = NULL;
    jint result = -1;

    if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        return result;
    }

    register_ndk_param(env);

    // 返回jni的版本
    return JNI_VERSION_1_4;
}

 

 

C实现Java的本地接口方法:

JNIEXPORT jint JNICALL
getPersonInfoByIndex(JNIEnv *env, jobject clazz, jobject person, jint index)
{
    // 若index无效,则直接返回-1。
    if ((int)index<0 || (int)index>=GPERSON_NUM)
        return -1;
    // 将Person数组(gPersons)中的第index个成员赋值给pPerson指针
    Person *pPerson = &gPersons[index];
    // 设置java对象person的mName
    jstring name = (*env)->NewStringUTF(env, pPerson->mName);
    (*env)->SetObjectField(env, person, gPersonOffsets.name, name);
    // 设置java对象person的mAge
    (*env)->SetIntField(env, person, gPersonOffsets.age, pPerson->mAge);
    // 设置java对象person的mHeight
    (*env)->SetFloatField(env, person, gPersonOffsets.height, pPerson->mHeight);

    return 0;
}
JNIEXPORT jint JNICALL getMyInfoByIndex(JNIEnv *env, jobject clazz, jobject myinfo, jint index)
{
    // 若index无效,则直接返回-1。
    if ((int)index<0 || (int)index>=GMYINFO_NUM)
        return -1;
    // 将Person数组(gPersons)中的第index个成员赋值给pPerson指针
    MyInfo *pMyInfo = &gMyInfos[index];
    // 设置java对象person的mName
    jstring name = (*env)->NewStringUTF(env, pMyInfo->mName);
    (*env)->SetObjectField(env, myinfo, gMyInfoOffsets.name, name);
    // 设置java对象person的mAge
    (*env)->SetIntField(env, myinfo, gMyInfoOffsets.age, pMyInfo->mAge);
    // 设置java对象person的mHeight
    (*env)->SetFloatField(env, myinfo, gMyInfoOffsets.height, pMyInfo->mHeight);
    return 0;
}

今日感受

JNI作为一个中间枢纽,他代码的编写也需要着结构化的思想,只要记住哪块对应哪块,哪块该干嘛,该绑定的绑定了没有,这样才能实现JNI的实际意义!