简单JNI的使用--在Java中调用C库函数
特别注意在windows下执行vc++的cl命令需要添加两处环境变量
%主目录%\Microsoft Visual Studio\Common\MSDev98\Bin
%主目录%\Microsoft Visual Studio\VC98\Bin
在Android Framework中,需要提供一种媒介或桥梁,将Java层(上层)与C/C++(底层)有机地联系起来,使得它们相互协调,
共同完成某些任务。在这两层之间充当连接桥梁这一角色的就是Java本地接口(JNI,Java Native Interface),
它允许Java代码与基于C/C++编写的应用和库进行交互操作。
JNI提供了一系列接口,允许Java类与使用C/C++等其它编程语言(在JNI中,这些语言被称为本地语言)编写的应用程序、模块、
库进行交互操作。比如,在Java类中使用C语言库中中的特定函数,或在C语言里面使用Java类库,都需要借助JNI来完成。
通常会在下列几种情况下使用JNI
注重处理速度:如果对某段程序的执行速度有较高的要求,建议使用C/C++编写代码,而后在Java层通过JNI调用基于C/C++编写的部分代码。
硬件控制:为了更好地控制硬件,硬件控制代码通常使用C语言编写,借助JNI将其与Java层连接起来,从而实现对硬件的控制。
已有C/C++代码的复用:在编写程序的过程中,常常会使用已经编写好的C/C++代码,既提高了编程效率,又确保了程序的安全性和健壮性。
在复用这些C/C++代码时,就要通过JNI来实现。
在Java代码中通过JNI调用C函数的步骤如下:
第一步:编写Java代码
第二步:编译Java代码(javac Java文件)
第三步:生成C代码头文件(javah java类名,自动生成)
第四步:编写C代码(实现C代码头文件里面的函数)
第五步:生成C共享库(使用工具编译生成C共享库,win下面为dll文件,Linux下面为so文件)
第六步:运行Java程序(java 类名)
第一步:编写Java代码
首先编写调用C语言的Java源代码HelloJNI.java
public class HelloJNI {
native void printHello(); 1
native void printString(String str);
static{
System.loadLibrary("hellojni"); 2
}
public static void main(String[] args) {
HelloJNI myJNI = new HelloJNI();
myJNI.printHello();
myJNI.printString("Hello world form printString function!");
}
}
说明:
1在Java类中,使用”native”关键字,声明本地方法,该方法与用C/C++编写的JNI本地函数相对应。
”native”关键字告知Java编译器,在Java代码中带有该关键字的方法只是声明,具体由C/C++等其它语言编写实现。
2在Java类中声明了本地方法之后,接下来,调用System.loadLibrary()方法,加载具体实现本地方法的C运行库
(在Java中加载本地运行库通常使用静态块(static block))。System.loadLibrary()
方法加载由字符串参数指定的本地库,在不同操作系统平台下,加载的C运行库不同。在Window下面,调用
System.loadLibrary(“hellojni”),则hellojni.dll会被加载;在Linux下面,
则会加载libhellojni.so文件。
第二步:编译Java代码
使用如下命令编译java源代码:
javac HelloJNI.java
编译好HelloJNI.java后,生成HelloJNI.class文件。如果此时直接运行java程序,就是抛出异常。
由于尚未创建加载到Java代码中的hellojni.dll库文件,无法找到Java虚拟机要加载的C运行库。
接下来,创建hellojni.dll库文件。
第三步:生成C代码头文件
若想创建本地方法的映射C函数,必须先生成函数原型,函数原型存在于C/C++头文件中。Java提供了javah工具,位于JAVA
JDK的安装目录的bin目录下面,用来生成包含函数原型的C/C++头文件,使用方法如下:
javah <包含以native关键字声明方法的Java类名称>
运行javah命令,会在当前目录下生成与Java类名(即javah命令的参数)相同名称的C语言头文件。在生成的C头文件中,
定义了与Java本地方法相链接的C函数原型。
以下为生成的HelloJNI.h文件内容:
/* DO NOT EDIT THIS FILE - it is machine generated */ 1
#include <jni.h>
/* Header for class HelloJNI */
#ifndef _Included_HelloJNI
#define _Included_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloJNI
* Method: printHello
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_HelloJNI_printHello
(JNIEnv *, jobject);
/*
* Class: HelloJNI 2
* Method: printString
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_HelloJNI_printString
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
说明:
1该文件由javah命令生成,为了保证JNI正常运行,请不要直接修改本文件的内容,JNI开发者只要使用C/C++语言实现定义
的函数即可。
2这是javah命令生成的两个C函数原型,函数原型在Java类中声明的本地方法的基础上生成。查看各函数原型注释,可以看到与
各函数原型对应的Java代码中的本地方法,注释中标明了三个元素:类名、本地方法、本地方法签名。
接下来分析一下函数原型:JNIEXPORT、JNICALL都是JNI关键字,表示此函数要被JNI调用,函数原型中必须有这两个关键字,
JNI才能正常调用函数。其实,JNIEXPORT、JNICALL两个关键字都是宏定义,
在JDK_HOME/include/win32/jni_md.h文件(在Window平台下)中。
观察函数原型名称,可以发现函数名称遵循一定的命名规则:JNI支持的函数命名形式为”Java_类名_本地方法名”。
通过函数命名即可推断出JNI本地函数与哪个Java类的哪个本地方法相对应。
在生成的函数原型中,带有两个默认参数,分别为JNIEnv * 与jobject,支持JNI的函数必须包含这两个共同参数。
第一个参数JNIEnv *为JNI接口指针,用来调用JNI表中的各种JNI函数(这里的JNI函数是指JNI中提供的基本函数集);
第二个参数jobject也是JNI提供的Java本地方法,用来在C代码中访问Java对象,此参数中保存着调用本地方法的对象的一个引用。
在JNI编程中,Java程序与C/C++函数间经常进行数据交换,如果不提供一种方法消除两种语言的数据类型的差异,
那么程序就无法正常运行,运行的可靠性也无法保障。JNI提供了一套与Java数据类型相对应的Java本地类型,
使得本地语言可以使用Java数据类型,如下表所示:
Java类型
本地类型
字节(bit)
boolean-jboolean
8,
byte-jbyte
8
char-jchar
16,
short-jshort
16
int-jint
32
long-jlong
64
float-jfloat
32
double-jdouble
64
void-void
n/a
以上Java本地类型定义在JDK_HOME/include/jni.h文件中。此外,Java本地类型也提供了另外三种类型,
分别对应于Java类、对象与字符串三种引用类型数据(除此之外还有几种引用类型,有兴趣可以可以查阅相关资料)。
Java引用类型
Java本地类型
类
Jclass
对象
Jobject
String-Jstring
第四步:编写C代码
在C函数原型生成后,开始编写hellojni.c文件,具体实现JNI本地函数。首先,把定义在HelloJNI.h头文件中的函数原型
复制到hellojni.c,注意在使用javah命名生成的头文件中,函数的参数仅指定了参数的类型,并未给出参数的名称,
因此复制完函数原型,开始实现C函数时,必须先在参数类型后指定具体的参数名称。
以下为编写好的hellojni.c代码:
#include "HelloJNI.h"
#include <stdio.h>
//添加名称为env与obj的两个参数
JNIEXPORT void JNICALL Java_HelloJNI_printHello(JNIEnv *env, jobject obj)
{
printf("Hello world!\n");
return ;
}
JNIEXPORT void JNICALL Java_HelloJNI_printString(JNIEnv *env, jobject obj,
jstring string)
{
//将Java String转换以C字符串
const char * str = (*env)->GetStringUTFChars(env,string,0);
printf("%s!\n",str);
return ;
}
GetStringUTFChars ()是JNI函数,用来将Java字符串转换成C语言字符串。JNI提供了多种JNI函数,
用来处理C字符串与Java字符串的转换,具体可以参考:
第五步:生成C共享库
在编写好了hellojni.c之后,使用编译器将其编译成hellojni.dll文件。这里使用的是
Visual C++ 2008 Express Editions。安装好之后,使用Visual Studio 2008 命令提示,
输入编译指令:
cl -I "F:\Java\jdk1.7.0\include" -I "F:\Java\jdk1.7.0\include\win32"
-LD hellojni.c -Fe hellojni.dll
执行结果为:
结果会显示
/dll
/implib:hellojni.lib
/out:hellojni.dll
hellojni.obj
正在创建库 hellojni.lib 和对象 hellojni.exp
指令说明:
cl:visual c++编译命令
-I<dir>:添加要检索头文件的目录路径<dir>
为了检索头文件,添加如下目录
jni.h(<JDK_HONE>\include)
jni_md.h(JDK_HONE>\include\win32)
-LD:创建DLL
-Fe<文件名>:指定编译结果文件名称
第六步:运行Java程序
执行java指令,运行HelloJNI类。
特别注意在windows下执行vc++的cl命令需要添加两处环境变量
%主目录%\Microsoft Visual Studio\Common\MSDev98\Bin
%主目录%\Microsoft Visual Studio\VC98\Bin
在Android Framework中,需要提供一种媒介或桥梁,将Java层(上层)与C/C++(底层)有机地联系起来,使得它们相互协调,
共同完成某些任务。在这两层之间充当连接桥梁这一角色的就是Java本地接口(JNI,Java Native Interface),
它允许Java代码与基于C/C++编写的应用和库进行交互操作。
JNI提供了一系列接口,允许Java类与使用C/C++等其它编程语言(在JNI中,这些语言被称为本地语言)编写的应用程序、模块、
库进行交互操作。比如,在Java类中使用C语言库中中的特定函数,或在C语言里面使用Java类库,都需要借助JNI来完成。
通常会在下列几种情况下使用JNI
注重处理速度:如果对某段程序的执行速度有较高的要求,建议使用C/C++编写代码,而后在Java层通过JNI调用基于C/C++编写的部分代码。
硬件控制:为了更好地控制硬件,硬件控制代码通常使用C语言编写,借助JNI将其与Java层连接起来,从而实现对硬件的控制。
已有C/C++代码的复用:在编写程序的过程中,常常会使用已经编写好的C/C++代码,既提高了编程效率,又确保了程序的安全性和健壮性。
在复用这些C/C++代码时,就要通过JNI来实现。
在Java代码中通过JNI调用C函数的步骤如下:
第一步:编写Java代码
第二步:编译Java代码(javac Java文件)
第三步:生成C代码头文件(javah java类名,自动生成)
第四步:编写C代码(实现C代码头文件里面的函数)
第五步:生成C共享库(使用工具编译生成C共享库,win下面为dll文件,Linux下面为so文件)
第六步:运行Java程序(java 类名)
第一步:编写Java代码
首先编写调用C语言的Java源代码HelloJNI.java
public class HelloJNI {
native void printHello(); 1
native void printString(String str);
static{
System.loadLibrary("hellojni"); 2
}
public static void main(String[] args) {
HelloJNI myJNI = new HelloJNI();
myJNI.printHello();
myJNI.printString("Hello world form printString function!");
}
}
说明:
1在Java类中,使用”native”关键字,声明本地方法,该方法与用C/C++编写的JNI本地函数相对应。
”native”关键字告知Java编译器,在Java代码中带有该关键字的方法只是声明,具体由C/C++等其它语言编写实现。
2在Java类中声明了本地方法之后,接下来,调用System.loadLibrary()方法,加载具体实现本地方法的C运行库
(在Java中加载本地运行库通常使用静态块(static block))。System.loadLibrary()
方法加载由字符串参数指定的本地库,在不同操作系统平台下,加载的C运行库不同。在Window下面,调用
System.loadLibrary(“hellojni”),则hellojni.dll会被加载;在Linux下面,
则会加载libhellojni.so文件。
第二步:编译Java代码
使用如下命令编译java源代码:
javac HelloJNI.java
编译好HelloJNI.java后,生成HelloJNI.class文件。如果此时直接运行java程序,就是抛出异常。
由于尚未创建加载到Java代码中的hellojni.dll库文件,无法找到Java虚拟机要加载的C运行库。
接下来,创建hellojni.dll库文件。
第三步:生成C代码头文件
若想创建本地方法的映射C函数,必须先生成函数原型,函数原型存在于C/C++头文件中。Java提供了javah工具,位于JAVA
JDK的安装目录的bin目录下面,用来生成包含函数原型的C/C++头文件,使用方法如下:
javah <包含以native关键字声明方法的Java类名称>
运行javah命令,会在当前目录下生成与Java类名(即javah命令的参数)相同名称的C语言头文件。在生成的C头文件中,
定义了与Java本地方法相链接的C函数原型。
以下为生成的HelloJNI.h文件内容:
/* DO NOT EDIT THIS FILE - it is machine generated */ 1
#include <jni.h>
/* Header for class HelloJNI */
#ifndef _Included_HelloJNI
#define _Included_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloJNI
* Method: printHello
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_HelloJNI_printHello
(JNIEnv *, jobject);
/*
* Class: HelloJNI 2
* Method: printString
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_HelloJNI_printString
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
说明:
1该文件由javah命令生成,为了保证JNI正常运行,请不要直接修改本文件的内容,JNI开发者只要使用C/C++语言实现定义
的函数即可。
2这是javah命令生成的两个C函数原型,函数原型在Java类中声明的本地方法的基础上生成。查看各函数原型注释,可以看到与
各函数原型对应的Java代码中的本地方法,注释中标明了三个元素:类名、本地方法、本地方法签名。
接下来分析一下函数原型:JNIEXPORT、JNICALL都是JNI关键字,表示此函数要被JNI调用,函数原型中必须有这两个关键字,
JNI才能正常调用函数。其实,JNIEXPORT、JNICALL两个关键字都是宏定义,
在JDK_HOME/include/win32/jni_md.h文件(在Window平台下)中。
观察函数原型名称,可以发现函数名称遵循一定的命名规则:JNI支持的函数命名形式为”Java_类名_本地方法名”。
通过函数命名即可推断出JNI本地函数与哪个Java类的哪个本地方法相对应。
在生成的函数原型中,带有两个默认参数,分别为JNIEnv * 与jobject,支持JNI的函数必须包含这两个共同参数。
第一个参数JNIEnv *为JNI接口指针,用来调用JNI表中的各种JNI函数(这里的JNI函数是指JNI中提供的基本函数集);
第二个参数jobject也是JNI提供的Java本地方法,用来在C代码中访问Java对象,此参数中保存着调用本地方法的对象的一个引用。
在JNI编程中,Java程序与C/C++函数间经常进行数据交换,如果不提供一种方法消除两种语言的数据类型的差异,
那么程序就无法正常运行,运行的可靠性也无法保障。JNI提供了一套与Java数据类型相对应的Java本地类型,
使得本地语言可以使用Java数据类型,如下表所示:
Java类型
本地类型
字节(bit)
boolean-jboolean
8,
byte-jbyte
8
char-jchar
16,
short-jshort
16
int-jint
32
long-jlong
64
float-jfloat
32
double-jdouble
64
void-void
n/a
以上Java本地类型定义在JDK_HOME/include/jni.h文件中。此外,Java本地类型也提供了另外三种类型,
分别对应于Java类、对象与字符串三种引用类型数据(除此之外还有几种引用类型,有兴趣可以可以查阅相关资料)。
Java引用类型
Java本地类型
类
Jclass
对象
Jobject
String-Jstring
第四步:编写C代码
在C函数原型生成后,开始编写hellojni.c文件,具体实现JNI本地函数。首先,把定义在HelloJNI.h头文件中的函数原型
复制到hellojni.c,注意在使用javah命名生成的头文件中,函数的参数仅指定了参数的类型,并未给出参数的名称,
因此复制完函数原型,开始实现C函数时,必须先在参数类型后指定具体的参数名称。
以下为编写好的hellojni.c代码:
#include "HelloJNI.h"
#include <stdio.h>
//添加名称为env与obj的两个参数
JNIEXPORT void JNICALL Java_HelloJNI_printHello(JNIEnv *env, jobject obj)
{
printf("Hello world!\n");
return ;
}
JNIEXPORT void JNICALL Java_HelloJNI_printString(JNIEnv *env, jobject obj,
jstring string)
{
//将Java String转换以C字符串
const char * str = (*env)->GetStringUTFChars(env,string,0);
printf("%s!\n",str);
return ;
}
GetStringUTFChars ()是JNI函数,用来将Java字符串转换成C语言字符串。JNI提供了多种JNI函数,
用来处理C字符串与Java字符串的转换,具体可以参考:
第五步:生成C共享库
在编写好了hellojni.c之后,使用编译器将其编译成hellojni.dll文件。这里使用的是
Visual C++ 2008 Express Editions。安装好之后,使用Visual Studio 2008 命令提示,
输入编译指令:
cl -I "F:\Java\jdk1.7.0\include" -I "F:\Java\jdk1.7.0\include\win32"
-LD hellojni.c -Fe hellojni.dll
执行结果为:
结果会显示
/dll
/implib:hellojni.lib
/out:hellojni.dll
hellojni.obj
正在创建库 hellojni.lib 和对象 hellojni.exp
指令说明:
cl:visual c++编译命令
-I<dir>:添加要检索头文件的目录路径<dir>
为了检索头文件,添加如下目录
jni.h(<JDK_HONE>\include)
jni_md.h(JDK_HONE>\include\win32)
-LD:创建DLL
-Fe<文件名>:指定编译结果文件名称
第六步:运行Java程序
执行java指令,运行HelloJNI类。
java HelloJNI
android技术上如有疑问可以问我,有问必答.
爱品茶的盆友,光顾小店(谢谢,能收藏最好了):
专注移动开发!继续前行~