JNI入门介绍

最近做了一些JNI相关的工作,所以顺便了解了一下JNI,并整理了这篇文章,文章分几段讲:

最简入门程序

helloworld程序既然作为上手的第一个程序,就要求代码上和介绍必须精简,作为技术的介绍者有义务做到,复杂的信息和代码只会让人望而却步。这里就只放了最简单的几句代码,不过“麻雀虽小,五脏俱全”。

1、使用Elipse创建一个项目,并创建一个类:

public class JNIClass {
	
	public native void JNIFunc();

	public static void main(String[] args) {
		System.loadLibrary("JNI_Implement");
		new JNIClass().JNIFunc();
	}
	
}

2、导出native函数的声明:

>call "C:\Program Files\Java\jdk1.6.0\bin\javah.exe" -classpath ./bin -jni -o JNI_CDeclare.h blazefire.test.JNIClass
如果预先将javah的路径添加到环境变量了,也可以直接

>javah -classpath ./bin -jni -o JNI_CDeclare.h blazefire.test.JNIClass
生成一个文件"JNI_CDeclare.h",包含以下函数声明:

JNIEXPORT void JNICALL Java_blazefire_test_JNIClass_JNIFunc
  (JNIEnv *, jobject);
3、创建一个VS项目,类型选择dll,添加一个文件"JNI_CImplement.cpp":

#include<stdio.h>
#include "JNI_CDeclare.h"

JNIEXPORT void JNICALL Java_blazefire_test_JNIClass_JNIFunc
  (JNIEnv *, jobject)
{
	printf("JNI_CTest");
}
4、把"JNI_CDeclare.h"复制到vs项目对应的目录下,同时添加以下路径到"项目"->"属性"->"配置属性"->"C/C++"->"常规"->"附加包含目录"

C:\Program Files\Java\jdk1.6.0\include

C:\Program Files\Java\jdk1.6.0\include\win32

5、编译生成"JNI_Implement.dll",把它复制到Eclipse项目目录下。

6、运行java项目,查看结果。

如何入门

一开始我本人也并不了解JNI,因此我去网上搜索“C调用Java”,马上得出了一堆网址,以下是其中之一:http://public0821.iteye.com/blog/423941。

于是照着上面的做,还好jdk是老早就装了的,VS和Eclipse也有(2008版),因此直接开始起代码。

然后卡在某一个地方动不了了:JNI_CreateJavaVM,函数调用始终失败。如果看到这篇文章的人成功调用了这个函数,请留言,因为我到现在还没过这一步。说“调用失败”,其实也不完全准确,因为在调试过程中发现整个进程直接退出了,大概是里面碰到了严重错误因此exit(1)了。看名字,JNI_CreateJavaVM函数目的是为了创建一个虚拟机,事实上我用不到它,相信绝大部分使用JNI的人都和我一样,只是想用C给java做扩展,而并不是希望自己创建虚拟机。

因此我转变了思路,尝试用Java调用C。我看的文章是http://chnic.iteye.com/blog/228096。

原理很简单:在java的某一个类中声明一个native方法,然后用javah工具导出C函数声明,然后用C实现这个函数,并且生成一个dll,然后java启动时加载这个dll,此时调用这个native函数其实执行的就是dll中的可执行代码而非java字节码。总共算是5步吧:声明native,javah,写函数,建立dll项目,加载dll。我照着文章做就行了,一切OK。

在C调用java的那篇文章中使用JNI_CreateJavaVM的目的无非也就是创建一个虚拟机句柄jvm(JavaVM *)和一个JNI环境句柄env(JNIEnv *),事实上jvm在JNI使用过程中极少用到,用得多的是env,因此不建议初次接触JNI的程序员使用JNI_CreateJavaVM来获取env,使用native声明+C实现,JVM则由Eclipse创建,这样可以直接获取到env,减少了很多麻烦

用C还是C++的问题

使用javah导出的头文件,并非必须要被代码包含,只要保证JVM调用native函数时,在dll中能找到这个函数即可。也即:javah工具只是告诉你,当JVM在运行时调用这个native函数会以怎样的函数名去找C函数!如果想要检查你生成的dll是否能让JVM找到,可以使用工具Dependency Walker打开生成的dll文件,看里面是否有需要的导出函数。

从这个角度来说,使用C语言和C++语言也没有任何区别,因为二进制代码和dll格式不是由语言决定的。但是需要注意的是函数名修饰问题,如果此文的读者不是很了解请点击这里:http://blog.csdn.net/masefee/article/details/4123210。同样一个函数,使用C编译和使用C++编译,可能会有不同的函数名,自然,dll导出函数名也会不同。JNI需要的函数必须是C规范下的__stdcall调用约定,否则是找不到函数的!不过如果包含了javah生成的头文件,它在函数声明中会强制JNI的C函数使用C的编译方式编译(#ifdef __cplusplus ...),这是一个老方法了,相信大家都知道。

还有一个C和C++的区别就是对于env的使用,C语言中必须使用(*env)->JNIFunction(env, ...)的调用方式,而C++使用env->JNIFunction(...)调用。C语言需要手动传入env的原因很明显,是因为C不存在类似于__thiscall这样的调用约定,不会自动传入this指针。至于前半部分的区别(*env)则是因为裸露虚函数表的问题,env表示上下文,*env表示虚函数表。C里面想要实现env->JNIFunction其实也可以,不过代价略大了一点,需要把所有的函数地址都复制到对象内,这些和本文无关,不做赘述。

类名参数名问题

java中的包名+类名都是用“.”区分,例如java.lang.String,但是在JNI中,查找一个class必须使用“/”作为区分,例如env->FindClass("java/lang/String"),这样才能正确找出一个class。

那如果一上来找不到class怎么办?其实没有关系,只要把native函数不要加static,这样JNI_C函数的第二个参数(第一个是env)就会是obj,然后使用env->GetObjectClass(obj)获取class。

另外,关于参数符号。想要查找一个class的某一个方法(为了获取methodID),除了需要传入函数名之外,参数列表(包括返回值)也需要明确给出。原因是java类的方法是可以重载的,光有函数名无法定位唯一的一个方法。参数列表是一个类似于"(III)V"格式的字符串。如果想知道一个类型对应的符号可以随便写一个native函数,带有那种类型的参数,然后在JNI_C函数声明中看到底是什么符号。这里需要纠正之前提到的一篇文章的错误:http://chnic.iteye.com/blog/228096,"long"类型的符号是"J"而不是"L"!

路径问题和杂项

实现了JNI_C函数之后,可以把代码放到dll工程中编译,生成dll可以不放在eclipse项目的根目录,例如我放在"myfolder/MyJNI_CLib.dll"中,那么试图加载库文件就需要调用System.LoadLibrary("myfolder/MyJNI_CLib")。

不过有一个问题,就是如果在JNI_C函数中引用相对路径,还是以eclipse的根目录为基本路径。比如在dll位置为"myfolder/MyJNI_CLib.dll",函数中调用fopen("files/MyFile.txt", "w"),实际打开的文件"files/MyFile.txt"而不是"myfolder/files/MyFile.txt"。

“杂项”一词是为了以后如果发现了什么可以补充在这里。暂时只有一条:

JNI_C函数的命名是固定的,我之前在android上使用JNI时,因为一直出不了结果,导致我有点怀疑是android上不能使用javah导出声明。其实大家可以一百个放心,如果发现程序有问题,绝对不会是“导出函数名不对”这种问题。

其他一些废话

总的而言,java与C交互相对还是比较复杂的,我这么说是基于比较了java、lua、.NET三种虚拟机语言之后才得出的结论。

lua本身就是嵌入式的脚本语言,它除了提供完整的语言解析器(llex.c/lparser.c)和运行时(lvm.c)之外,还提供一整套luaC API,让程序员可以在C语言中直接操作虚拟机对象。

.NET其实和java有点像,也是高内聚的虚拟机,不过相比而言.NET比java复杂许多,声明一个native调用只需要在.NET语言(例如C#.NET或VB.NET)里面声明哪一个模块的哪一个函数名就行了,它把所有复杂的东西都集成到虚拟机里面。至于跨平台性……人家反正只有windows上能用,所以完全不需要考虑跨平台。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值