[TOC]
在学习这部分内容之前,先回去看看c语言的动态库加载。
java JNI有两种方法:
- 一般方法
主要是通过javah获取函数的签名,然后实现这些函数,这也是java官方推荐的方法。实现相对简单。 - JNI_OnLoad 方法
如果在c库文件中没有实现JNI_OnLoad 方法,则默认使用上述普通方法,如果实现了JNI_OnLoad 方法,那么java程序在调用System.loadLibrary()或者System.load()方法后,dvm就会调用dvmResolveNativeMethod来动态解析。对应的也有析构方法:JNI_OnUnLoad()
**使用JNI_OnLoad有两个用处: **
a. 告诉 JVM,此 C 组件使用的哪个版本的 JNI,如果 c 库中没有JNI_OnLoad那么JVM默认使用最老的JNI1.1版本。这样新的JNI版本中的扩充就无法使用。
b. C 组件可以在JNI_OnLoad中自定义一些初始化动作。
此方法使用简单,对 C 组件要求低。大致流程如下:
- java 中 使用 System.loadLibrary()或者System.load() 来加载 C 库 ,然后把需要用C/C++实现的函数声明为native方法(加关键字native即可),最后便可以直接使用。
- 编译java程序得到class文件,然后使用javah工具得到JNI头文件,里面包含了这些native方法,在 c/c++ 中的函数原型。
- 在 c 代码中包含jni.h 和 这个生成的头文件,实现定义的native方法。
- 最后讲 c 代码编译成 动态库即可。
public class JNIDemo {
/* 利用静态代码块,加载c库,后面介绍loadLibrary和load方法的区别
* 这里写的时hello,在Linux下,会自动去库的路径下查找 “libhello.so”,即libxxx.so
*/
static {
System.loadLibrary("hello");
}
/* 假设我们的java程序这两个函数需要用c或者c++来实现,所以声明为本地(native)方法
* 声明native方法很简单,只需要加上关键字native即可。
* 这个关键字告诉javac编译器,此方法时本地方法,其实现不在java文件中,不要给我报错!!!
*/
public static native int sayHello(String name);
public static native String sayGoodbye(String name);
public static void main(String[] args) {
/* 在java中便可以直接调用native方法了。 */
System.out.println("the return value of sayHello is : " +
sayHello("zhangsan"));
System.out.println("the return value of sayGoodbye is : " +
sayGoodbye("lisi"));
}
} /* a. 编译 java 文件,和编译一般的java没任何区别 */
$shell javac JNIDemo.java
/* b. 使用javah 得到头文件 -jni 参数可以不要 */
$shell javah -jni JNIDemo
就会在当前目录下生成 JNIDemo.h我们可以看看其具体内容:
JNIDemo.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class JNIDemo */
#ifndef _Included_JNIDemo
#define _Included_JNIDemo
#ifdef __cplusplus
extern "C" { // 这里需要注意下:如果实现的文件时cpp文件, 这里的意思是,下面的函数要被c语言调用。
#endif
/*
* Class: JNIDemo
* Method: sayHello
* Signature: (Ljava/lang/String;)I // 关于这些,在JNI_Onload中要用到,后面再说
*/
JNIEXPORT jint JNICALL Java_JNIDemo_sayHello
(JNIEnv *, jclass, jstring);
/*
* Class: JNIDemo
* Method: sayGoodbye
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_JNIDemo_sayGoodbye
(JNIEnv *, jclass, jstring);
#ifdef __cplusplus
}
#endif
#endif#include <stdio.h>
#include <jni.h>
#include "JNIDemo.h"
JNIEXPORT jint JNICALL Java_JNIDemo_sayHello
(JNIEnv *env, jclass cls, jstring name) {
int ret = 100; // just for a test;
char* str_name=(char*) env->GetStringUTFChars(name,JNI_FALSE);
printf("cpp : hello %s\n",(char*)str_name);
env->ReleaseStringUTFChars(name,str_name);
return ret;
}
JNIEXPORT jstring JNICALL Java_JNIDemo_sayGoodbye
(JNIEnv *env, jclass cls, jstring name) {
/* 从jstring中获取字符串 ,后面详细介绍*/
char* str_name=(char*) env->GetStringUTFChars(name,JNI_FALSE);
printf("call sayGoodbye %s\n",str_name);
env->ReleaseStringUTFChars(name,str_name);
return 0;
}因为我们在c代码中使用到了两个头文件jni.h和 JNIDemo.h 所以在编译时要用 -I 选项他们的路径,也可以把JNIDemo.h 拷贝到c目录下,但是jni.h 除了 它自己以外还需要别的头文件,所以不能直接拷贝。
- 首先找到jni.h 的目录 jni.h 时随着jdk一起发布的,jni属于jdk的一部分。所以在jdk中。
$shell find /usr/local/java/jdk1.8.0_131/ -name "jni.h"
/usr/local/java/jdk1.8.0_131/include/jni.h
在编译时我们发现,除了jni.h 还需要别的头文件,在LInux上还需要包含:
/usr/local/java/jdk1.8.0_131/include/linux 目录下的头文件- 编译动态库
/* 编译选项就不多解释了,用的很多了 */
$shell g++ -shared -fPIC -I/usr/local/java/jdk1.8.0_131/include -I/usr/local/java/jdk1.8.0_131/include/linux -o libhello.so hello.cpp 上看我们也说了,java中加载动态库或者说windows下的共享库时,有两个方法。loadLibrary 方法和 load 方法。他们的区别在于:
-
System.loadLibrary("hello")
loadLibrary() 参数为库名xxx。那么默认会加载libxxx.so 或者libxxx.dll
查找路径:由jvm变量:java.library.path 指定。默认情况下这个变量包含了:
1.和jre相关的一些目录
2.程序当前目录
3.Windows目录
4.系统目录(system32),Linux下为/lib ,/usr/lib
5.系统环境变量path指定目录 -
System.load("/home/coewang/work/java_project/0001th_java_jni/src/cpp_src/libhello.so")
load 传入的必须是完整的动态库全路径,包含后缀名。 缺点:
如果你要载入的库文件静态链接到其它动态链接库,例如TestJNI.dll 静态链接到dependency.dll, 那么你必须注意 如果你选择System.load("D://aaa//TestJNI.dll");
那么即使你把dependency.dll同样放在D://aaa//下,load还是会因为找不到依赖的dll而失败。
因为jvm在载入TestJNI.dll会先去载入TestJNI.dll所依赖的库文件dependency.dll
而dependency.dll并不位于java.library.path所指定的目录下,所以jvm找不到dependency.dll。
所以你要先laod依赖库,再load这个库。或者把目录加入到java.library.path 中。
我们使用了System.loadLibrary("hello"),所以要指定java.library.path
$shell java -Djava.library.path=../cpp_src JNIDemo
基本数据类型在java和c/c++ 中可以直接使用。他们的对应关系如下表所示:
| java | 本地类型 | 占用字节数 |
|---|---|---|
| boolean | jboolean | 8,unsigned |
| byte | jbyte | 8 |
| char | jchar | 16,unsigned |
| short | jshort | 16 |
| int | jint | 32 |
| long | jlong | 64 |
| float | jfloat | 64 |
| double | jdouble | 64 |
| void | void | n/a |
- jstring不能在c中被直接转换为char* 使用,我们可以想象一下,在 java 中 String 是一个类,它里面包含了字符数组和众多方法,要想把一个String 对象传递到 c 中,肯定是以某种数据结构打包发送到 c ,c中需要调用解析这个数据结构的函数来获得数据和对象中的方法。 下面是访问String的一些方法:
注意!!! 这些方法在C和CPP中有所不同!!!
| C语言 : JNIEnv中从jstring获取字符串的方法 | 描述 |
|---|---|
const char* (JNICALL *GetStringUTFChars) (JNIEnv *env, jstring str, jboolean *isCopy) |
将 jstring 转换成为 UTF-8 格式的char* |
void (JNICALL *ReleaseStringUTFChars) (JNIEnv *env, jstring str, const char* chars) |
释放指向UTF-8格式的char*的指针 |
const jchar *(JNICALL *GetStringChars) (JNIEnv *env, jstring str, jboolean *isCopy) |
将jstring转换成为Unicode格式的char* |
void (JNICALL *ReleaseStringChars) (JNIEnv *env, jstring str, const jchar *chars) |
释放指向Unicode格式的char*的指针 |
实质上在成c++中这些函数是对c函数的一个封装,把c函数中第一个参数使用this代替。
| C++ : JNIEnv中从jstring获取字符串的方法 | 描述 |
|---|---|
const char* GetStringUTFChars(jstring str, jboolean *isCopy) |
将 jstring 转换成为 UTF-8 格式的char* |
void ReleaseStringUTFChars(jstring str, const char* chars) |
释放指向UTF-8格式的char*的指针 |
const jchar *GetStringChars(jstring str, jboolean *isCopy) |
将jstring转换成为Unicode格式的char* |
void ReleaseStringChars(jstring str, const jchar *chars) |
释放指向Unicode格式的char*的指针 |
**在调用方面,cpp中使用 env->xxxx(...) 因为在c++中 env 为对象; ** *在c语言中使用 (env)->xxx(env,...) ,在c中 env 是 2重指针 以上结论对所有函数实用,下面只介绍c++的,c语言可以推出来,加env即可
测试代码 c++:
JNIEXPORT jint JNICALL Java_JNIDemo_sayHello
(JNIEnv *env, jclass cls, jstring name) {
int ret = 100; // just for a test;
/* 第二个参数指定是不是要拷贝,还是直接引用 */
char* str_name=(char*) env->GetStringUTFChars(name,JNI_FALSE);
printf("cpp : hello %s\n",(char*)str_name);
/* 使用之后切记要释放,否则会造成内存的泄露 */
env->ReleaseStringUTFChars(name,str_name);
return ret;
}- 同样的c\c++中的字符串也是不能直接转化为jstring返回给java程序的。对于字符串封装为jstring,jni为我们提供了线面几个常用的函数: 具体操作流程看下面的测试代码。
| C++ : JNIEnv中将字符串封装成jstring的方法 | 描述 |
|---|---|
jstring NewStringUTF(const char *utf) |
创建一个UTF-8格式的String对象 |
jstring NewString(const jchar *unicode, jsize len) |
创建一个Unicode格式的String对象 |
jsize GetStringUTFLength(jstring str) |
获取 UTF-8格式的char*的长度 |
jsize GetStringLength(jstring str) |
获取Unicode格式的char*的长度 |
C语言中都是加上一个env参数。
测试代码 c ++:
JNIEXPORT jstring JNICALL Java_JNIDemo_sayGoodbye
(JNIEnv *env, jclass cls, jstring name) {
char* str_name=(char*) env->GetStringUTFChars(name,JNI_FALSE);
printf("call sayGoodbye %s\n",str_name);
env->ReleaseStringUTFChars(name,str_name);
/* 使用 字符串构造 jstring对象 */
jstring str = env->NewStringUTF("hahahaha");
return str;
}- java向c++传递类对象
JNIEXPORT jobject JNICALL Java_com_oracle_estt_sc_db_impl_SCQueryODBC__1getCustomer
(JNIEnv *env, jobject, jobject customer){
jmethodID methodId;
//获得customer对象的句柄
jclass cls_objClass=env->GetObjectClass(customer);
//获得customer对象中特定方法getName的id
methodId=env->GetMethodID(cls_objClass,"getName","()Ljava/lang/String;");
//调用customer对象的特定方法getName
jstring js_name=(jstring)env->CallObjectMethod(customer,methodId,NULL);
...
}- c++向java返回对象
JNIEXPORT jobject JNICALL Java_com_oracle_estt_sc_db_impl_SCQueryODBC__1getCustomer
(JNIEnv *env, jobject, jobject customer){
......
//发现java Customer类,如果失败,程序返回
jclass clazz = env->FindClass("com/oracle/estt/sc/busi/Customer");
if(clazz == 0)
return 0;
//为新的java类对象obj分配内存
jobject obj = env->AllocObject(clazz);
//发现类中的属性,如果失败,程序返回
jfieldID fid_id = env->GetFieldID(clazz,"customerID","I");
if (fid_id == 0)
return 0;
jfieldID fid_name = env->GetFieldID(clazz,"name","Ljava/lang/String;");
if (fid_name == 0)
return 0;
......
env->SetIntField(obj, fid_id, 1
env->SetObjectField(obj, fid_name, jname);
......
return obj;
}- java向 c++ 传递对象数组
对于这种情况,先得到数组的大小,接下来取出数组中的对象,取得对象的属性值或者调用对象的方法,将获得值存到本地数组中,然后可以灵活使用这些数 据了。举例说明:java向c传递一个含有多个customer对象的数组,在c中将这个数组的分解出来,存到本地的临时数组中去。
JNIEXPORT void JNICALL Java_com_oracle_estt_sc_db_impl_SCInsertODBC__1insertCustomeRequest___3Lcom_oracle_estt_sc_busi_CustomerRequest_2
(JNIEnv *env, jobject, jobjectArray oa){
......
//声明customerrequest对象
jobject o_customer;
int i;
jmethodID methodId;
jint size=env->GetArrayLength(oa);
_tmp_bind[0]= (char *)malloc(size*sizeof(int));
_tmp_bind[1]= (char *)malloc(size*sizeof(char)*( 20 + 1));
...
//将输入数组的数据拷贝到临时数组中去
for(i=0;i<size;i++){
//从数组中获得customerrequest对象
o_request=env->GetObjectArrayElement(oa,i);
//获得customerrequest对象的句柄
jclass cls_objClass=env->GetObjectClass(o_request);
//获得customerrequest对象的特定方法getCustomerID的id
methodId=env->GetMethodID(cls_objClass,"getCustomerID","()I");
//调用customerrequest对象的特定方法getCustomerID
int_customerID=env->CallIntMethod(o_request,methodId,NULL);
//获得customerrequest对象中特定方法getTelNum的id
methodId=env->GetMethodID(cls_objClass,"getTelNum","()Ljava/lang/String;");
//调用customerrequest对象的特定方法getTelNum
str_telNum=(jstring)env->CallObjectMethod(o_request,methodId,NULL);
...
//将用户id拷贝到临时数组
memcpy(_tmp_bind[0]+i*sizeof(int),&int_customerID,sizeof(int));
//将电话号码拷贝到临时数组,如果电话号码字符串超长,报错返回
if(sizeof(char)*strlen(chr_tel)<=sizeof(char)*( 20 + 1)){
memcpy(_tmp_bind[1]+i*sizeof(char)*( 20+1 ),chr_tel,strlen(chr_tel)+1);
}else{
printf("%s too long!\n",chr_tel);
return;
}
...
}
...
}- c++ 向 java 返回对象数组
先创建数组,然后加载java对象,给每个java对象的属性赋值,添加到数组中,最后返回数组。如下例:
JNIEXPORT jobjectArray JNICALL Java_com_oracle_estt_sc_db_impl_SCQueryODBC__1getCustomerRequest
(JNIEnv *env, jobject, jint customerid){
......
//声明存放查询结果的objectarray
jobjectArray jo_array = env->NewObjectArray(MAX_LINE,env->FindClass("com/oracle/estt/sc/busi/CustomerRequest"), 0); jobject obj;
//发现java Customerrequest类,如果失败,程序返回
jclass clazz = env->FindClass("com/oracle/estt/sc/busi/CustomerRequest");
if(clazz == 0)
return 0;
while ((rc = SQLFetch(hstmt)) == SQL_SUCCESS ||rc == SQL_SUCCESS_WITH_INFO) {
obj = env->AllocObject(clazz);
jfieldID fid_customerID = env->GetFieldID(clazz,"customerID","I");
if (fid_customerID == 0)
return 0;
jfieldID fid_priority = env->GetFieldID(clazz,"priority","I");
if (fid_priority == 0)
return 0;
...
env->SetIntField(obj, fid_customerID, col_customerID);
env->SetIntField(obj, fid_priority, col_priority);
...
//将对象obj添加到object array中
if(j<MAX_LINE){
env->SetObjectArrayElement(jo_array, j, obj);
}else{
break;
}
}
return jo_array;
}
- java层
/* java jni 数据传递,综合测试 */
class Student {
private String name;
private int age;
/* constructor */
/* setter and getter */
/* show method */
public String show() {
return "[ " + this.name + "," +this.age + " ]";
}
}
public class JNIDemo {
static {
System.loadLibrary("hello");
}
/* 1. 基本数据类型 , 计算两个数的和并返回 */
public static native int add(int a,int b);
/* 2. 基本数据类型数组,每一项对应相加,存入数组返回 */
public static native int[] sum(int[] a, int[] b);
/* 3. String -- char * ,提取子串并返回 */
public static native String subString(String src,int start,int len);
/* 4. 对象传递 , 修改对象属性,构造新的对象并返回 */
public static native Student changeStudentInfo(Student stu);
/* 5. 对象数组 ,修改多个 */
public static native Student[] changeAll(Student[] studets);
public static void sop(Object o) {
System.out.println("[ in java ] : " + o );
}
public static void main(String[] args) {
sop("add(5 , 6) = " + add(5,6));
int[] a = {1,2,3};
int[] b = {3,2,1};
int[] c = sum(a,b);
sop("sum( [1,2,3] , [3,2,1] ) = " + c[0] + ","+ c[1]+ ","+c[2]);
sop("subString(abcdef,2,3) = " + subString("abcdef",2,3));
Student liming = new Student("liming",25);
sop("Student(liming,25) ---> after change() ..." );
Student ret = changeStudentInfo(liming);
sop("before change ..." + ret.show());
Student[] students = { new Student("zhangsan",20) ,
new Student("lisi",21),
new Student("wangwu",20)};
}
}- 编译java,生成jni所需要的头文件
$shell javac JNIDemo.java
$shell javah -jni JNIDemo
现在目录下有:
JNIDemo.class
JNIDemo.h
JNIDemo.java
Student.class- 编写c程序(使用cpp)
打开刚才生成的JNIDemo.h 可以看到所有native方法的定义。把这个头文件拷贝到c程序目录下。
====== JNIDemo.h =======
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class JNIDemo */
#ifndef _Included_JNIDemo
#define _Included_JNIDemo
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: JNIDemo
* Method: add
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_JNIDemo_add
(JNIEnv *, jclass, jint, jint);
/*
* Class: JNIDemo
* Method: sum
* Signature: ([I[I)[I
*/
JNIEXPORT jintArray JNICALL Java_JNIDemo_sum
(JNIEnv *, jclass, jintArray, jintArray);
/*
* Class: JNIDemo
* Method: subString
* Signature: (Ljava/lang/String;II)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_JNIDemo_subString
(JNIEnv *, jclass, jstring, jint, jint);
/*
* Class: JNIDemo
* Method: changeStudentInfo
* Signature: (LStudent;)LStudent;
*/
JNIEXPORT jobject JNICALL Java_JNIDemo_changeStudentInfo
(JNIEnv *, jclass, jobject);
/*
* Class: JNIDemo
* Method: changeAll
* Signature: ([LStudent;)[LStudent;
*/
JNIEXPORT jobjectArray JNICALL Java_JNIDemo_changeAll
(JNIEnv *, jclass, jobjectArray);
#ifdef __cplusplus
}
#endif
#endif 然后在cpp中实现这些方法:
hello.cpp
/*
* hello.cpp jvav JNI 测试用 动态库源文件
* by codewang 2017.7.17 @heu
*/
#include <stdio.h>
#include <stdlib.h>
/*
* 对动态库的一些封装和对java JNI库函数的支持
*/
#include <jni.h>
#include "JNIDemo.h"
/* 对于java中的int add(int int) */
JNIEXPORT jint JNICALL Java_JNIDemo_add
(JNIEnv *env, jclass cls, jint a, jint b)
{
/* 基本数据类型,直接使用 */
return a+b;
}
/* 对应java中的 int[] sum(int[] ,int[]) */
JNIEXPORT jintArray JNICALL Java_JNIDemo_sum
(JNIEnv *env, jclass cls, jintArray a, jintArray b)
{
jint *arr_a,*arr_b;
jint len = env->GetArrayLength(a);
printf("arr len = %d\n",len);
// 注意,这里不释放会造成内存泄漏。
// 一般我们在java层提供一个释放native方法,在cpp中释放内存
jint* ret = new jint[len];
arr_a = env->GetIntArrayElements(a,JNI_FALSE);
arr_b = env->GetIntArrayElements(b,JNI_FALSE);
for(int i=0;i<len;i++) {
ret[i] = arr_a[i] + arr_b[i];
}
/* 构造 jintArray返回给java */
jintArray jintarray = env->NewIntArray(len);
env->SetIntArrayRegion(jintarray,0,len,ret);
return jintarray;
}
/* 对应java中的 String subString(String ,int ,int) */
JNIEXPORT jstring JNICALL Java_JNIDemo_subString
(JNIEnv *env, jclass cls, jstring str, jint start, jint len)
{
/* 从jstring中获取字符串 */
char *src = (char*)env->GetStringUTFChars(str,JNI_FALSE);
/* 分离子串 */
char* subStr = new char[len+1];
for(jint i=start,j=0;j<len;i++,j++) {
subStr[j] = src[i];
}
subStr[len] = '\0';
/* 封装为jstring 返回 */
jstring retString = env->NewStringUTF(subStr);
/* 释放 */
env->ReleaseStringUTFChars(str,src);
return retString;
}
/* 对应java中的 Student changeStudentInfo(Student ) */
JNIEXPORT jobject JNICALL Java_JNIDemo_changeStudentInfo
(JNIEnv *env, jclass cls, jobject student)
{
/* 先获得类 */
jclass classStudent = env->GetObjectClass(student);
/* 获得方法 */
jmethodID getName = env->GetMethodID(classStudent,
"getName",
"()Ljava/lang/String;");
/* 执行方法 */
jstring name = (jstring)env->CallObjectMethod(student,getName,NULL);
char *src = (char*)env->GetStringUTFChars(name,JNI_FALSE);
printf("int cpp : get name = %s\n",src);
/* 返回新对象 */
jobject newObject = env->AllocObject(classStudent);
/* 可以通过调用方法设置属性,也可以直接设置 */
jfieldID nameID = env->GetFieldID(classStudent,"name","Ljava/lang/String;");
jfieldID ageID = env->GetFieldID(classStudent,"age","I");
jstring codewang = env->NewStringUTF("codewang");
env->SetObjectField(newObject,nameID,codewang);
env->SetIntField(newObject,ageID,22);
return newObject;
}
JNIEXPORT jobjectArray JNICALL Java_JNIDemo_changeAll
(JNIEnv *, jclass, jobjectArray)
{
/* 和 intArray操作 一样,省略了。。。 */
}- 编译动态库
$shell g++ -fPIC -shared -o libhello.so hello.cpp -I/usr/local/java/jdk1.8.0_131/include/ -I/usr/local/java/jdk1.8.0_131/include/linux
切记这里如果是cpp代码,一定要用g++,如果用gcc会出现找不到符号_Znam 错误。
- 运行java测试
$shell java -Djava.library.path=../cpp_src JNIDemo
运行结果:
[ in java ] : add(5 , 6) = 11
arr len = 3
[ in java ] : sum( [1,2,3] , [3,2,1] ) = 4,4,4
[ in java ] : subString(abcdef,2,3) = cde
[ in java ] : Student(liming,25) ---> after change() ...
int cpp : get name = liming
[ in java ] : before change ...[ codewang,22 ]
相比于直接使用javah获得函数签名,实现本地方法的方式,使用JNI_OnLoad 方法更加灵活,在Android中也是使用这个方法来实现JNI应用到Android的硬件服务系统中。
在java代码中调用System.loadLibrary()或者System.load() 方法后,java虚拟机首先调用dlopen打开动态库,然后查询有没有JNI_OnLoad方法,如果没有就是用默认的最老JNI版本,如果有则先调用JNI_OnLoad方法,所以我们的JNI_OnLoad 方法至少要设置JNI版本。
下面以例子程序讲解JNI_Onload的使用过程:
/* 假设下面时在c中实现的本地方法,方法体忽略,我们只关注参数类型 */
jint func1(jint ,jint){}
jintArray func2(jIntArray){}
jstring func3(jstring){}
jobject func4(jobject){}
jObjectArray func5(jObjectArray){}
/* 定义方法映射的数组 */
/*
* 关于这些参数符号表,怎么定义,实质上在我们用javah生成的头文件里有
* 大概规则为:
* 1. (参数列表)返回值
* 2. [ 代表数组
* 3. L 代表对象
* 4. 基本数据类型由一个字母代替,见下面的表
* 5. String对象比较特殊,要用java/lang/String 表示
* 6. 其他对象均被转化为Onject类
* 我们再看看这个JNINativeMethod结构体如何定义的:
* typedef struct {
* char *name; // java中对应的方法名
* char *signature; // 参数返回值符号表
* void *fnPtr; // c中的对应的函数指针
* } JNINativeMethod;
*/
static const JNINativeMethod gMethods[] = {
{ "java_func1" , "(II)I" , (void*)func1 },
{ "java_func2" , "([I)[I" , (void*)func2 },
{ "java_func3" , "(Ljava/lang/String)Ljava/lang/String" , (void*)func3 },
{ "java_func4" , "(LObject)Lobject" , (void*)func4 } ,
{ "java_func5" , "([LObject)[LObject" , (void*)func5}
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
{
/* 在前面我们实验时,都知道第一个参数时一个env,如果没有这个函数,jvm默认给我们最
*老版本的JNI ,但是有了初始化函数,我们可以选择JNI版本。
*/
JNIEnv * env;
vm->GetEnv((void**)&env,JNI_VERSION_1_4); // 获取版本1.4
/* 注册方法 */
/* 1. 注册方法,要注册到哪个类? */
jclass clazz = env->FindClass("JNIDemo");
/* 2. 注册到这个类 */
env->RegisterNatives(clazz,gMethods,5);
/* 可以想象一下:
* 这中方法实质上和前面一种方法的区别就在于,改了一个env,那么我们在c程序中更改了env,JVM却不知道啊,
* 所以我们要把选择的版本告诉JVM
*/
return JNI_VERSION_1_4;
}
/*
* 除了Onload 之外 ,还有与之对应的OnUnLoad,它在动态库卸载之前被调用,可以联想下c的动态库连接技术
* 还有很多函数供我们使用,可以在jni.h中看到他们的定义。
*/
| java类型 | c中的类型 | 符号 |
|---|---|---|
| Boolean | jboolean | Z |
| Byte | jbyte | B |
| Char | jchar | C |
| Short | jshort | S |
| Int | jint | I |
| Long | jlong | L |
| Float | jfloat | F |
| Double | jdouble | D |
| void | jvoid | V |
| String | jstring | Ljava/lang/String |
| 对象 | jobject | 以"L"开头,以";"结尾,中间是用"/" 隔开的包及类名。如果是嵌套类,则用$来表示嵌套 |
| 数组 | jXxxArray | 用"["加上如此表所示的对应类型的简写形式 |