说明
本人从事Android开发工作,目前在研究JNI相关知识,关于JNI的学习,个人认为应该先从JNI本身入手,然后逐步扩展到Android方向,而不是直接开始Android方向JNI的开发和学习。为什么使用VC++ 6.0 ,因为大多数人大学学习C/C++的时候使用过这个工具,小巧,高效,虽然目前使用它开发时不现实的,但是用来学习还是很不错的。
JNI编写流程
编写带有native声明的方法的java类
使用javac命令编译所编写的java类
然后使用javah + java类名生成扩展名为h的头文件
使用C/C++实现本地方法
将C/C++编写的文件生成动态连接库
运行java工程并调用JNI
编写带有native声明的方法的java类
使用之前需要注意的是,一定要用32位的JDK,为什么?因为我们要利用vc++6.0生成dll库文件,但是vc++6.0是不支持生成64位dll的,所以JDK需要使用32位,不明白没关系,我们待会详细说。
HelloJni.java
public class HelloJni{
static{
System.loadLibrary("HelloJni");
}
public native static void hello(String s);
public native static String say();
public static void main(String[] args){
HelloJni hello = new HelloJni();
hello.hello("Hello JNI !");
System.out.println(hello.say());
}
}
使用javac命令编译所编写的java类
进入java文件目录,生成class文件。
javac HelloJni.java
然后使用javah + java类名生成扩展名为h的头文件
javah HelloJni
可以看到该目录下生成了一个HelloJni.h
的文件,主要内容是
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloJni */
#ifndef _Included_HelloJni
#define _Included_HelloJni
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloJni
* Method: hello
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_HelloJni_hello
(JNIEnv *, jclass, jstring);
/*
* Class: HelloJni
* Method: say
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_HelloJni_say
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
该文件里面包含了需要实现的本地方法申明。
对于已生成的.h 头文件, C/C++所需要做的,就是把它的各个方法具体的实
现。然后编译连接成库文件,如果是在Linux环境下面就是Android里面常见的so库,而Windows下面则是dll文件。
在具体实现的时候,我们只关心两个函数原型
NIEXPORT void JNICALL Java_HelloJni_hello(JNIEnv *, jclass, jstring);
NIEXPORT void JNICALL Java_HelloJni_hello(JNIEnv *, jclass, jstring);
这里 JNIEXPORT 和 JNICALL 都是 JNI 的关键字,表示此函数是要被 JNI 调用的。而
jstring
是JNI中定义的一种数据类型,目的是使得java的String类型和本地代表字符序列(c/c++中的字符数组)能够相互转化,我们可以把它当作一种中间类型,jstring
暂且可以当做String
来看待。可以看到函数的名称是 Java_再加上 java 类的 package路径再加函数名组成的。JNIEnv*和 jclass 我们暂时可以忽略。
使用C/C++实现本地方法
知道我们要实现的本地方法之后,我们可以用c++来实现这两个方法,并编译链接成dll文件。
我们新建一个HelloJni.cpp
文件,将要实现的方法拷贝进去,并将HelloJni.c
包含进去。
#include "HelloJni.h"
const char* s;
JNIEXPORT void JNICALL Java_HelloJni_hello(JNIEnv * env, jclass cls, jstring str)
{
s = env->GetStringUTFChars(str, 0);
}
JNIEXPORT jstring JNICALL Java_HelloJni_say(JNIEnv * env, jclass cls)
{
return env->NewStringUTF(s);
}
将C/C++编写的文件生成动态连接库
实现好本地方法之后,接下来就是编译并链接成dll文件了。
打开vc6.0,我们新建一个Win32 Dynamic-Link Library
工程,并勾选一个简单的DLL工程
。
我们可以看到该工程里面已经有一个HelloJni.cpp
文件了.
#include "stdafx.h"
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call,LPVOID lpReserved)
{
return TRUE;
}
注意#include "stdafx.h"
待会会用到,接下来,这时候我们将之前生成的HelloJni.c
和创建的HelloJni.cpp
复制并覆盖到工程目录下,将#include "stdafx.h"
添加到我们实现的HelloJni.cpp
里面去,那么工程就变成这样。
#include "stdafx.h"
#include "HelloJni.h"
const char* s;
JNIEXPORT void JNICALL Java_HelloJni_hello(JNIEnv * env, jclass cls, jstring str)
{
s = env->GetStringUTFChars(str, 0);
}
JNIEXPORT jstring JNICALL Java_HelloJni_say(JNIEnv * env, jclass cls)
{
return env->NewStringUTF(s);
}
点击Build
之后编译没有问题进入到HelloJni工程目录下面的Debug
目录下面就可以找到HelloJni.dll
文件。那么dll文件就生成好了。
如果点击
Compile
之后报错,没有关系,先点击上面的Build
之后再点Compile
就会发现没有错误了。
运行java工程并调用JNI
拿到dll文件之后,将dll拷贝到HelloJni.class目录下面,运行java程序。
java HelloJni
输出
Hello JNI !
如果你是用的64位JDK的话,那么就会报错。
Exception in thread "main" java.lang.UnsatisfiedLinkError: C:\Users\johnny.wu\Desktop\HelloJni.dll: Can't load IA 32-bit .dll on a AMD 64-bit platform
所以这里需要32位的JDK,如果必须用64位的JDK那么就只能使用比VC6.0更强大的编译器生成64位的dll文件。