JavaSE JNI 动态注册本地方法(c语言实现native层)

来源:互联网 发布:淘宝nb官方旗舰店 编辑:程序博客网 时间:2024/05/29 05:03

转载请注明出处:http://blog.csdn.net/geekdonie/article/details/15805573


引言:

最近结合着 Android 源码研究了一下 JNI ,发现 Android 上的 JNI 本地方法绑定使用的不是通过函数名进行绑定的静态绑定,而是使用了不常见的动态绑定。

于是在 JavaSE 中动手实现了一下 JNI 本地方法动态绑定,在实现过程中或多或少的出现了一些问题,而网上搜索到的相关文章大多数只是对 Android 源码的一个摘要,而没有具体在 JavaSE 中的实现,故作此文以补此空白。

本文不涉及 JNI 基础,仅仅是对本地方法动态绑定的一个讲解,不明白 JNI 为何物的小白同学请先 Google 研究一下 JNI 再来~~


动态注册本地方法需要一下几个数据结构与方法(可自行在 jni.h 中查看):

[c] view plaincopy
  1. // 该结构表示一个Java本地方法与native层的c方法的映射  
  2. typedef struct {  
  3.     char *name; // Java本地方法名称  
  4.     char *signature; // Java本地方法签名  
  5.     void *fnPtr; // c函数指针  
  6. } JNINativeMethod;  
  7.   
  8. // 该方法在JVM加载完JNI动态库的紧接着被调用,所以可以在这个函数里进行动态注册  
  9. JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved);  
  10.   
  11. // 该方法便是用来进行动态注册的  
  12. // clazz:本地方法所在类  
  13. // methods:表明方法映射关系的结构体数组  
  14. // nMethods:上一个参数的数组大小  
  15. jint (JNICALL *RegisterNatives) (JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint nMethods);  


首先看一下声明和调用本地方法的 Java 代码:

[java] view plaincopy
  1. public class test {  
  2.   
  3.     static {  
  4.         // 加载JNI动态库  
  5.         System.loadLibrary("test_native");  
  6.     }  
  7.   
  8.     public static native String nativeFunc(String msg);  
  9.   
  10.     public static void main(String[] args) {  
  11.   
  12.         System.out.println(nativeFunc("Hello Native"));  
  13.   
  14.     }  
  15. }  

在这个代码中声明了一个 native 方法 nativeFunc,该方法的作用是将传入的字符串加上"(Sovled)"并然后返回修改后的字符串。


然后看一下 JNI 动态链接库的代码(C语言实现):

[c] view plaincopy
  1. #include <stdio.h>  
  2. #include <string.h>  
  3. #include <jni.h>  
  4. #include "test.h"  
  5.   
  6. jstring native_function(JNIEnv *env, jclass cls, jstring msg);  
  7.   
  8. // 定义Java本地方法与JNI动态库中方法的映射关系  
  9. static JNINativeMethod gMethods[] = {  
  10.     {"nativeFunc""(Ljava/lang/String;)Ljava/lang/String;", (void *)native_function}  
  11. };  
  12.   
  13.   
  14. // 注册本地方法  
  15. static int register_native_methods(JNIEnv *env, const char *clsname, const JNINativeMethod *gMethods, int nums)  
  16. {  
  17.     jclass clazz = (*env)->FindClass(env, clsname);  
  18.     if (clazz == NULL)  
  19.         return JNI_FALSE;  
  20.     if ((*env)->RegisterNatives(env, clazz, gMethods, nums) < 0)  
  21.         return JNI_FALSE;  
  22.     return JNI_TRUE;  
  23. }  
  24.   
  25. JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)  
  26. {  
  27.     JNIEnv *env = NULL;  
  28.     jint result = -1;  
  29.   
  30.     // 获得JNIEnv  
  31.     if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_4) != JNI_OK)  
  32.         return JNI_FALSE;  
  33.   
  34.     // 注册本地方法  
  35.     if (register_native_methods(env, "test", gMethods, sizeof(gMethods) / sizeof(gMethods[0])) != JNI_TRUE)  
  36.         return JNI_FALSE;  
  37.   
  38.     result = JNI_VERSION_1_4;  
  39.     return result;  
  40. }  
  41.   
  42. // 自定义方法,将msg加上"(Solved)"并返回  
  43. jstring native_function(JNIEnv *env, jclass cls, jstring msg)  
  44. {  
  45.     const char *cmsg;  
  46.     char cres[1024];  
  47.     jstring res;  
  48.   
  49.     cmsg = (*env)->GetStringUTFChars(env, msg, JNI_FALSE);  
  50.       
  51.     memset(cres, '\0', 1024);  
  52.     strcat(cres, cmsg);  
  53.     strcat(cres, " (Solved)");  
  54.     res = (*env)->NewStringUTF(env, cres);  
  55.       
  56.     (*env)->ReleaseStringUTFChars(env, msg, cmsg);  
  57.   
  58.     return res;  
  59. }  

这里注意 JNI_OnLoad 方法中的操作,在 Android 源码中,获取 JNIEnv 的代码是 vm->GetEnv((void**) &env, JNI_VERSION_1_4),如果在我们的程序中这样写是错误的,会出现 request for member ‘GetEnv’ in something not a structure or union (或 在非结构或联合中请求成员‘GetEnv’)错误,这是因为 Android 源码中的 native 层的实现使用的是 C++,而我们这里使用的是 C语言,这也是网上搜到的很多文章出现的一个问题


最后便是对代码的编译和运行:

1. 编译生成动态链接库 libtest_native.so:

gcc -fPIC -I $JAVA_HOME/include -I $JAVA_HOME/include/linux -shared -o libtest_native.so test.c

这里的 gcc 选项 -fPIC -shared 是用来生成动态链接库的,PIC选项的作用是生成 Position Independent Code,即生成不依赖与特定地址的机器代码,详细请看:http://stackoverflow.com/questions/5311515/gcc-fpic-option

2. 编译 Java 代码并运行

javac test.java

java -Djava.library.path=. test

注意:这里运行 Java 程序时需要加上 -Djava.library.path=. 来指定库加载位置,否则 Java 程序找不到之前生成的动态链接库。

0 0
原创粉丝点击