实现一个简单的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目录,所有编译后的文件都会存放在这个目录中。
接下来,直接在IDEA的Terminal窗口运行javah:
javah -classpath out\production\untitled -d ./jni com.jyf.Hello.java
接下来我们只需实现JNICALL Java_com_jyf_Hello_sayHello(JNIEnv *, jobject)即可。仔细观察就会发现这个函数名称是有规律的,即Java_<包>_<类名>_<函数名>;
一键生成头文件
Idea工具下,只需配置如下:
File>Settings>Tools>External Tools:
添加一个先的External Tools:
程序路径:$JDKpath$/bin/javah
参数:-jni -classpath $OutputPath$-d ./jni $FileClass$
项目名称:$ProjectFileDir$($ModuleFileDir$)
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文件在项目的根目录下:
然后将.o文件生成.dll文件
gcc -Wl,--add-stdcall-alias -shared -o HelloImpl.dll HelloImpl.o
也可以通过一个命令 将.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。
路径结构类似于-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$)
最后让java.library.path应该指定目录为lib
运行
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、定义本地方法(要实现的业务逻辑)
2、定义要加载的库()
3、实现JNI
至此,所有java文件写完,可以build工程项目,产生.Class文件。再运行插件,实现Javah命令,产生.h文件于jni文件中。
在jni文件中编写.c(.cpp)文件并修改.h文件。
修改.h文件:
- 1 加载所需的本地类
- 2 绑定需要注册的类
- 3 定义jfieldID结构体,用于保存类实体类的filedID
- 4 与实体类对应的结构体,用于保存数据,并将数据赋值给实体类
1.5 .h文件如果要存储值,需要定义数组空间
编写.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的实际意义!