JNI 学习手记
2013-11-28 11:26
309 查看
-——————12月27号追加说明——————
关于javah的用法:jdk6和jdk7的javah的用法是不一样的。对于jdk6,也就是我下文的主要环境,是对.class文件进行操作的:javah MyClass.class但是jdk7是针对命名空间进行操作的:
javah MyClass也就是说,当 .java文件是在一个包里的,如下:
package MyPackage; public class MyCode { public static native void myNative(); public static void main (String[] args) { myNative(); } }jdk6的javah和一般的并没有区别,而jdk7则要知道命名空间后才能用:
javah MyPackage.MyCode从语义来讲,jdk7的更加统一了,然而从某种程度上破坏了用jdk6开发的代码的一致性
———————原文————————
近来要做java的一个项目,必须和外部设备通信,因此内核必须是用C来完成。然而C写交互界面过于麻烦,因此上层必须用Java实现,最后考虑下来的结果,就是用JNI来调用C的接口,让工作可以尽量迅速而简单地完成。工作平台是Debian,详细的系统和硬件信息在最后,本文结合完全本人编写的代码进行讨论。如果大家看了有什么想法,或者有什么问题,希望提出来探讨探讨。
JNI手册地址:JNI Document
一 Hello World
这一节是牛刀小试,主要是先尝试在JVM上run C code,先编写java代码:public class HelloWorld { public native void showHelloWorld(); static { System.loadLibrary("hello"); } public static void main(String[] args) { new HelloWorld().showHelloWorld(); } }
然后编译:
hu@forhu-debian:~/Java code/JNI code/Helloworld$ javac HelloWorld.java
这时候生成了.class文件,再用javah生成头文件:
hu@forhu-debian:~/Java code/JNI code/Helloworld$ javah HelloWorld.class
一般而言,不报错就是成功生成。下一步是根据头文件编写C文件:
#include <jni.h> #include "HelloWorld.h" #include <stdio.h> JNIEXPORT void JNICALL Java_HelloWorld_showHelloWorld (JNIEnv *env, jobject obj){ printf("Hello world!\n"); return; }然后再只要生成动态链接库就可以完成工作:
hu@forhu-debian:~/Java code/JNI code/Helloworld$ gcc -std=c99 -shared -o ~/lib/libhello.so HelloWorld.c再run java程序就能看到结果:
hu@forhu-debian:~/Java code/JNI code/Helloworld$ java HelloWorld Hello world!这时候要注意是否把.so文件生成到了动态链接的路径上,如果没有,可以手动添加这个路径到LD_LIBRARY_PATH环境变量:
export LD_LIBRARY_PATH=$HOME/lib
二 解析头文件
看JNI的doc里的resolving native method names一节里面解释了C里面的name mangling的规范,这个其实和C++的规范差不多,学过点C++的应该就不会觉得陌生,先看头文件 :/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> #ifndef __HelloWorld__ #define __HelloWorld__ #ifdef __cplusplus extern "C" { #endif JNIEXPORT void JNICALL Java_HelloWorld_showHelloWorld (JNIEnv *env, jobject); #ifdef __cplusplus } #endif #endif /* __HelloWorld__ */
doc里的描述如下:
Dynamic linkers resolve entries based on their names. A native method name is concatenated from the following components:
the prefix
Java_
a mangled fully-qualified class name
an underscore (“_”) separator
a mangled method name
for overloaded native methods, two underscores (“__”) followed by the mangled argument signature
分别解释一下就是:
一个比如那存在的Java_ 前缀
然后接着是类的全名 (包括包)
然后是下划线_
然后是一个mangle过的方法名
JNI中mangle的意思就是当函数重载的时候加上__后在标上传入参数的签名,这个特性在这里没有体现
具体的签名规范看doc的第三章有讲述:Type Signatures
在知道这些命名规范的前提下,我们就可以知道在C下实现的函数对应于Java中的哪个方法了
三 返回数组
这一节尝试返回一个double数组,其他数组应该是同理:public class GDA { protected static native double[] genDoubleArray(int length); static { System.loadLibrary("GenDoubleArr"); } public static void main (String[] args){ int limit=10; double[] p=genDoubleArray(limit); for (int i=0; i<limit; i++) System.out.println(i+": "+p[i]); } }
C语言中的实现:
#include <time.h> #include <stdlib.h> #include <stdio.h> #include <jni.h> #include "GDA.h" __attribute ((constructor)) void init_func(){ srand((unsigned)time(NULL)); printf("seed setted!\n"); } JNIEXPORT jdoubleArray JNICALL Java_GDA_genDoubleArray (JNIEnv *env, jclass clazz, jint length){ jdoubleArray gen_arr=(*env)->NewDoubleArray(env, length); if (gen_arr==NULL) { fprintf(stderr, "cannot create double array!\n"); return NULL; } double tmp[length]; for (int i=0; i<length; i++) tmp[i]=rand()/(double)RAND_MAX; // tmp[i]=(double)i; (*env)->SetDoubleArrayRegion(env, gen_arr, 0, length,tmp); return gen_arr; }
运行结果是:
hu@forhu-debian:~/Java code/JNI code/GenDoubleArr$ java GDA seed setted! 0: 0.5973263795475132 1: 0.29107160646983965 2: 0.20303377891100655 3: 0.3453450050881808 4: 0.21342075206964312 5: 0.19031665855567748 6: 0.8962435400561632 7: 0.7831179601061707 8: 0.8714664293785889 9: 0.8222254104084453
这里用到了JNI的环境接口,注意接口调用的方式,相应的原型是:
ArrayType New<PrimitiveType>Array(JNIEnv *env, jsize length);
void Set<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize len, const NativeType *buf);
注意这些原型调用的方式
四 字符串
这一节编辑字符串。字符串只是稍微有点麻烦,这应该和文字编码有关:public class Str { private static native String retStr(); private native String lowerCase(String str); static { System.loadLibrary("Str"); } public static void main (String[] agrs) { Str a= new Str(); System.out.println(a.lowerCase(retStr())); } }
实现:
#include <stdio.h> #include <stdlib.h> #include "Str.h" static void lower_case(char *c){ char tmp=*c; if (tmp>='A'&&tmp<='Z') tmp+=('a'-'A'); *c=tmp; } JNIEXPORT jstring JNICALL Java_Str_retStr (JNIEnv *env, jclass clazz){ char *hp="HELLO WORLD!"; jstring tmp=(*env)->NewStringUTF(env,hp); if (tmp==NULL){ fprintf(stderr, "Cannot create jstring!\n"); return NULL; } return tmp; } JNIEXPORT jstring JNICALL Java 4000 _Str_lowerCase (JNIEnv *env, jobject jo, jstring jstr){ char *cptr; int len=(int)(*env)->GetStringUTFLength(env,jstr); char *jcbuf=malloc(sizeof(char)*len+1); if (jcbuf==NULL) { fprintf(stderr, "memory allocation error!\n"); return NULL; } (*env)->GetStringUTFRegion(env, jstr, 0, len, jcbuf); jcbuf[len]='\0'; for (int i=0;i<len;i++) { cptr=jcbuf+i; lower_case(cptr); } jstring jret=(*env)->NewStringUTF(env, jcbuf); if (jret==NULL) { fprintf(stderr, "cannot create jstring!\n"); free((void *)jcbuf); return NULL; } free((void *)jcbuf); return jret; }对于C给出来的接口,最好是用UTF编码。否则会用jchar来包装,这个东西长度恒为16bits,对于ascii环境应该不太好,我对编码不太了解,这里做不了深层次的讨论。
字符串这一块我也有些把握不准,比如说'\0'返不返回来呢?或许我应该做个实验,迫于时间这个往后再做。
五 C通过JNI调用Java的方法和静态方法
同样的,下面的是java代码:public class CallMethod { private static native String toLowerCase (String str); private static native int getStr(String str); static { System.loadLibrary("CallMethod"); } public static void main (String[] args) { System.out.println(toLowerCase("HELLO WORLD!")); System.out.println(getStr("100")); } }
下面的是C实现:
#include <stdio.h> #include <stdlib.h> #include "CallMethod.h" JNIEXPORT jstring JNICALL Java_CallMethod_toLowerCase (JNIEnv *env, jclass clazz, jstring str){ if (str==NULL) return NULL; //first of all, get the class jclass jstring_c=(*env)->GetObjectClass(env, str); //then, obtain method from the class jmethodID to_lower_case=(*env)->GetMethodID(env, jstring_c, "toLowerCase", /*want to call toLowerCase()*/ "()Ljava/lang/String;"); /*the signature of the method. read charp 3 for details*/ if (to_lower_case==NULL) return NULL; //now, we can call the method jstring retstr=(jstring)(*env)->CallObjectMethod(env, str, to_lower_case); //there should be some arguments followed if the method has some if (retstr==NULL) return NULL; return retstr; } JNIEXPORT jint JNICALL Java_CallMethod_getStr (JNIEnv *env, jclass clazz, jstring jstr){ jclass jinteger_c=(*env)->FindClass(env,"java/lang/Integer"); if (jinteger_c==NULL) return 0; jmethodID parse_int=(*env)->GetStaticMethodID(env, jinteger_c, "parseInt", "(Ljava/lang/String;)I"); if (parse_int==NULL) return 0; jint reti=(*env)->CallStaticIntMethod(env, jinteger_c, parse_int, jstr); return reti; }
可以跑出下面的运行结果:
hello world! 100
总结下来,C通过jni调用方法和静态方法都是三步走:
方法步骤
先调用jclass GetObjectClass(JNIEnv *env, jobject obj);找到对应对象的类,
然后再调用
jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
找到需要的方法,这里主义name和sig要匹配,sig的规则在doc中有讲述Type Signatures
然后调用下面任意一个函数来完成调用,这三个方法本质是等同的,只是传入参数的形式不一样而已,躯体区别,doc有讲述
http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html#wp4256
NativeType Call<type>Method(JNIEnv *env, jobject obj, jmethodID methodID, ...); NativeType Call<type>MethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args); NativeType Call<type>MethodV(JNIEnv *env, jobject obj, jmethodID methodID, va_list args);
静态方法步骤
和方法类似jclass FindClass(JNIEnv *env, const char *name);
jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
NativeType CallStatic<type>Method(JNIEnv *env, jclass clazz, jmethodID methodID, ...); NativeType CallStatic<type>MethodA(JNIEnv *env, jclass clazz, jmethodID methodID, jvalue *args); NativeType CallStatic<type>MethodV(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);
六 另外的topic
。。待续附录:测试环境和硬件参数
hu@forhu-debian:~/Java code/JNI code/CallMethod$ cat /proc/version Linux version 3.2.0-4-686-pae (debian-kernel@lists.debian.org) (gcc version 4.6.3 (Debian 4.6.3-14) ) #1 SMP Debian 3.2.51-1 hu@forhu-debian:~/Java code/JNI code/CallMethod$ uname -a Linux forhu-debian 3.2.0-4-686-pae #1 SMP Debian 3.2.51-1 i686 GNU/Linux hu@forhu-debian:~/Java code/JNI code/CallMethod$ java -version java version "1.6.0_27" OpenJDK Runtime Environment (IcedTea6 1.12.6) (6b27-1.12.6-1~deb7u1) OpenJDK Client VM (build 20.0-b12, mixed mode, sharing)
相关文章推荐
- JNI学习1
- JNI学习之Invocation API
- Atlas学习手记(18):使用DragPanel实现拖放面板
- jni 学习(转自安卓巴士)
- JNI学习2
- JSON学习手记
- scala学习手记6 - 字符串与多行原始字符串
- android jni学习
- JNI 开发经典案例之——卸载APK 跳转到特定网页(一般为反馈页面)学习
- 《华清远见学习手记》之 在FS2410上搭建servfox视频服务器
- NDK开发笔记(三)---JNI的学习
- 假期PHP学习手记!!
- Android JNI开发姿势 学习 AS2.0以下打so包、2.0以上解决 include jni.h报红
- JNI的学习与实践所得(Android)
- (Windows Workflow Foundation)学习手记(一)
- GeoServer学习手记(三):GeoServer架构浅谈
- JNI 学习笔记
- 从零开始MDT2010学习手记(四) 导入操作系统、驱动和补丁
- hibernate学习手记(2)
- Java学习之通过JNI调用C/C++编写的dll链接库(图文教程)