JNI 学习手记

-——————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

分别解释一下就是:

  1. 一个比如那存在的Java_ 前缀
  2. 然后接着是类的全名 (包括包)
  3. 然后是下划线_
  4. 然后是一个mangle过的方法名
  5. 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_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)

1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值