package com.marakana.jniexamples; public class Hello { public static native void sayHi(String who, int times); // static { System.loadLibrary("hello"); // } public static void main(String[] args) { sayHi(args[0], Integer.parseInt(args[1])); // } }
JNI回调是指在c/c++代码中调用java函数,当在c/c++的线程中执行回调函数时,会导致回调失败。
其中一种在Android系统的解决方案是:
把c/c++中所有线程的创建,由pthread_create函数替换为由Java层的创建线程的函数AndroidRuntime::createJavaThread。
所谓的报错,无非就是JNIEnv是线程保存的,c/c++线程是没有保存JNIEnv的。
当你用的是第三方c库,线程是人家创建好的,就没办法解决了。
用g_Jvm->AttachCurrentThread(&env,NULL);能把当前c线程转为java线程。
当在一个线程里面调用AttachCurrentThread后,如果不需要用的时候一定要DetachCurrentThread,否则线程无法正常退出。
假设有c++函数:
首先在c++中定义回调函数指针:
然后在jni中实现回调函数,以及其他实现:
/* * Returns a new java.io.FileDescriptor for the given int fd. */ jobject jniCreateFileDescriptor(C_JNIEnv* env, int fd); /* * Returns the int fd from a java.io.FileDescriptor. */ int jniGetFDFromFileDescriptor(C_JNIEnv* env, jobject fileDescriptor); /* * Sets the int fd in a java.io.FileDescriptor. */ void jniSetFileDescriptorOfFD(C_JNIEnv* env, jobject fileDescriptor, int value);
jobject jniCreateFileDescriptor(C_JNIEnv* env, int fd) { JNIEnv* e = reinterpret_cast<JNIEnv*>(env); jobject fileDescriptor = (*env)->NewObject(e, gCachedFields.fileDescriptorClass, gCachedFields.fileDescriptorCtor); jniSetFileDescriptorOfFD(env, fileDescriptor, fd); return fileDescriptor; } int jniGetFDFromFileDescriptor(C_JNIEnv* env, jobject fileDescriptor) { JNIEnv* e = reinterpret_cast<JNIEnv*>(env); return (*env)->GetIntField(e, fileDescriptor, gCachedFields.descriptorField); } void jniSetFileDescriptorOfFD(C_JNIEnv* env, jobject fileDescriptor, int value) { JNIEnv* e = reinterpret_cast<JNIEnv*>(env); (*env)->SetIntField(e, fileDescriptor, gCachedFields.descriptorField, value); }
GetBooleanArrayElements,
GetByteArrayElements,
GetCharArrayElements,
GetShortArrayElements,
GetIntArrayElements,
GetLongArrayElements,
GetFloatArrayElements,
GetDoubleArrayElements;
ReleaseBooleanArrayElements,
ReleaseByteArrayElements,
ReleaseCharArrayElements,
ReleaseShortArrayElements,
ReleaseIntArrayElements,
ReleaseLongArrayElements,
ReleaseFloatArrayElements,
ReleaseDoubleArrayElements;
GetStringChars 获取字符串中的字符
GetStringLength 获取字符串的长度
GetStringUTFChars 获取字符串中得UTF字符
GetStringUTFLength 获取字符串UTF字符长度
NewString 创建新的字符串
NewStringUTF 创建新的UTF字符串
ReleaseStringChars 释放字符串字符
ReleaseStringUTFChars 释放字符串UTF字符
GetArrayLength 获取数组的长度
Get<type>ArrayElements 获取相应类型的数组元素
Release<type>ArrayElements 释放相应类型的数组元素
Get<type>ArrayRegion 获取相应类型的数组的区域元素
Set<type>ArrayRegion 设置相应类型的数组的区域元素
GetObjectArrayElement 获取对象类型的数组元素
SetObjectArrayElement 设置对象类型的数组元素
GetObjectClass 获取对象类
GetMethodID 获取方法ID
GetStaticMethodID 获取静态方法ID
Call<type>Method 调用返回值为<type>型的方法
CallStatic<returntype>Method 调用相应返回值类型的静态方法
GetFieldID 获取数据域标志
GetStaticFieldID 获取静态数据域标志
Get<type>Field 获取<type>数据域
Set<type>Field 设置相应类型的数据域
GetStatic<type>Field 获取相应类型的静态数据域
SetStatic<type>Field 设置相应类型的静态数据域
ExceptionClear 异常清除
ExceptionDescribe 输出异常调试信息
ExceptionOccurred 捕获异常
NewGlobalRef 创建一个全局引用
DeleteGlobalRef 删除一个全局引用
DeleteLocalRef 删除一个局部引用
MonitorEnter 监视线程进入同步块
MonitorExit 监视线程退出同步块
JNI 的某些数组和字符串类型转换
jbytearray转c++byte数组 C代码 收藏代码 jbyte * arrayBody = env->GetByteArrayElements(data,0); jsize theArrayLengthJ = env->GetArrayLength(data); BYTE * starter = (BYTE *)arrayBody; jbyteArray 转 c++中的BYTE[] C代码 收藏代码 //jbytearray strIn jbyte * olddata = (jbyte*)env->GetByteArrayElements(strIn, 0); jsize oldsize = env->GetArrayLength(strIn); BYTE* bytearr = (BYTE*)olddata; int len = (int)oldsize; C++中的BYTE[]转jbyteArray C代码 收藏代码 //nOutSize是BYTE数组的长度 BYTE pData[] jbyte *by = (jbyte*)pData; jbyteArray jarray = env->NewByteArray(nOutSize); env->SetByteArrayRegin(jarray, 0, nOutSize, by); jbyteArray 转 char * C代码 收藏代码 char* data = (char*)env->GetByteArrayElements(strIn, 0); char* 转jstring C代码 收藏代码 jstring WindowsTojstring(JNIEnv* env, char* str_tmp) { jstring rtn=0; int slen = (int)strlen(str_tmp); unsigned short* buffer=0; if(slen == 0) { rtn = env->NewStringUTF(str_tmp); } else { int length = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)str_tmp, slen, NULL, 0); buffer = (unsigned short*)malloc(length*2+1); if(MultiByteToWideChar(CP_ACP, 0, (LPCSTR)str_tmp, slen, (LPWSTR)buffer, length) > 0) { rtn = env->NewString((jchar*)buffer, length); } } if(buffer) { free(buffer); } return rtn; } 下面这个没有用过,刚看到,也写进来,以后如果遇到可以验证下看。 jstring 转 char* 或者 const char* C代码 收藏代码 // jstring str const char *key = env->GetStringUTFChars(str, 0); //jboolean isOffer jsClient->modify(key, isOffer); env->ReleaseStringUTFChars(str, key); JNI 返回 jbyteArray C代码 收藏代码 JNIEXPORT jbyteArray JNICALL Java_Test_getByteArray(JNIEnv *env, jobject obj) { jbyteArray firstMacArray = env->NewByteArray( 6 ); ...... jbyte *bytes = env->GetByteArrayElements( firstMacArray, 0); for ( int i = 0; i < sizeof( pAdapterInfo->Address ); i++ ) { bytes[ i ] = pAdapterInfo->Address[ i ]; } env->SetByteArrayRegion(firstMacArray, 0, 6, bytes ); return firstMacArray; } //jstring to char* C代码 收藏代码 char* jstringTostring(JNIEnv* env, jstring jstr) { char* rtn = NULL; jclass clsstring = env->FindClass("java/lang/String"); jstring strencode = env->NewStringUTF("utf-8"); jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B"); jbyteArray barr= (jbyteArray)env->CallObjectMethod(jstr, mid, strencode); jsize alen = env->GetArrayLength(barr); jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE); if (alen > 0) { rtn = (char*)malloc(alen + 1); memcpy(rtn, ba, alen); rtn[alen] = 0; } env->ReleaseByteArrayElements(barr, ba, 0); return rtn; } //char* to jstring C代码 收藏代码 jstring stoJstring(JNIEnv* env, const char* pat) { jclass strClass = env->FindClass("Ljava/lang/String;"); jmethodID ctorID = env->GetMethodID(strClass, "<init>", "([BLjava/lang/String;)V"); jbyteArray bytes = env->NewByteArray(strlen(pat)); env->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte*)pat); jstring encoding = env->NewStringUTF("utf-8"); return (jstring)env->NewObject(strClass, ctorID, bytes, encoding); } //将jstring类型转换成windows类型 C代码 收藏代码 char* jstringToWindows( JNIEnv *env, jstring jstr ) { int length = (env)->GetStringLength(jstr ); const jchar* jcstr = (env)->GetStringChars(jstr, 0 ); char* rtn = (char*)malloc( length*2+1 ); int size = 0; size = WideCharToMultiByte( CP_ACP, 0, (LPCWSTR)jcstr, length, rtn,(length*2+1), NULL, NULL ); if( size <= 0 ) return NULL; (env)->ReleaseStringChars(jstr, jcstr ); rtn[size] = 0; return rtn; } //将windows类型转换成jstring类型 C代码 收藏代码 jstring WindowsTojstring( JNIEnv* env, char* str ) { jstring rtn = 0; int slen = strlen(str); unsigned short * buffer = 0; if( slen == 0 ) rtn = (env)->NewStringUTF(str ); else { int length = MultiByteToWideChar( CP_ACP, 0, (LPCSTR)str, slen, NULL, 0 ); buffer = (unsigned short *)malloc( length*2 + 1 ); if( MultiByteToWideChar( CP_ACP, 0, (LPCSTR)str, slen, (LPWSTR)buffer, length ) >0 ) rtn = (env)->NewString( (jchar*)buffer, length ); } if( buffer ) free( buffer ); return rtn; } /*JNIEXPORT jstring JNICALL Java_test_cs_web_SWIFTAlianceCASmfTest_strcal (JNIEnv *env, jclass obj, jstring jstr1, jstring jstr2) { jbyteArray bytes = 0; jthrowable exc; char *pszResult = NULL; char *pszSTR1 = NULL; char *pszSTR2 = NULL; pszSTR1 = jstringTostring(env, jstr1); pszSTR2 = jstringTostring(env, jstr2); int nlen = sizeof(char)*(strlen(pszSTR1)+strlen(pszSTR2)); pszResult = (char*)malloc(nlen); strcpy(pszResult, pszSTR1); strcat(pszResult, pszSTR2); jstring jstrRe = stoJstring(env, pszResult); free(pszSTR1); free(pszSTR2); free(pszResult); return(jstrRe); } */ jni object的使用 每一个jni格式的dll中的object对应该java里面的一个类。 如下例有一个 ObjData类,类中有成员bData ,Len public class ObjData { public byte[] bData; public int Len; } //------------------------jni获得传过来的Object类型的变量objDataIn-------- jclass clazz =(env)->FindClass("ObjData"); //从传进来的对象中取出byte[] C代码 收藏代码 jfieldID byteData = (env)->GetFieldID(clazz,"bData","[B"); jbyteArray pDataIn = (jbyteArray) (env)->GetObjectField(objDataIn, byteData); jsize theArrayLeng = env->GetArrayLength(pDataIn); //byte[]转为BYTE[] C代码 收藏代码 jbyte * arrayBody = env->GetByteArrayElements(pDataIn,0); BYTE * jDataIn = (BYTE *)arrayBody; //将BYTE数组转为jarray C代码 收藏代码 jbyte* byte = (jbyte*)jDataOut; jbyteArray jarray = env->NewByteArray(theArrayLeng); env->SetByteArrayRegion(jarray, 0, theArrayLeng, byte); //给每一个实例的变量付值 C代码 收藏代码 (env)->SetObjectField(objDataIn,byteData,jarray); (env)->SetIntField(objDataIn,pDataInLen,jDataInLen); (env)->ReleaseByteArrayElements(pDataIn, arrayBody, 0); 其他参考: Java 通过JNI调用C或者CPP代码 http://blog.csdn.net/kenera/archive/2009/02/16/3895343.aspx http://apps.hi.baidu.com/share/detail/15732549 http://dniit.blog.163.com/blog/static/28012894200842810332491/ http://hi.baidu.com/liangwind/blog/item/7dcce2c9729d1d1e7f3e6f49.html
Java 的出现给大家开发带来的极大的方便。但是,如果我们有大量原有的经过广泛测试的非 Java 代码,将它们全部用 Java 来重写,恐怕会带来巨大的工作量和长期的测试;如果我们的应用中需要访问到特定的设备,甚至是仅符合公司内部信息交互规范的设备,或某个特定的操作系统才有的特性,Java 就显得有些力不从心了。面对这些问题,Sun 公司在 JDK1.0 中就定义了 JNI 规范,它规定了 Java 应用程序对本地方法的调用规则。
实现步骤及相关函数使用
本文将一步步说明在 Linux 平台下如何实现本地共享库与 Java 协同工作。Hello World 程序是目前标准的入门第一步,那么,我也以类似的应用最为样例。
jbyte *arr = env-> GetByteArrayElements(jarr, 0);
只有C++时才用
在C中的写法应该是
jbyte * arr = (*env)-> GetByteArrayElements(env,jarr, NULL);
---------------------------------------------------------------
在C的定义中,env是一个两级指针,而在C++的定义中,env是个一级指针
第一步,定义一个 Java 类 -- Hello. 它提供 SayHello 方法:
此时应注意两点:
1. 为要使用的每个本地方法编写本地方法声明,其声明方式与普通 Java 方法接口没什么不同,只是必须指定 native 关键字,如下所示:
public native void SayHello(String strName);
在这个函数中,我们将根据传进的人名,向某人问好。
2. 必须显式地加载本地代码库。我们需在类的一个静态块中加载这个库:
static
{
System.loadLibrary("hello");
}
|
再加上必要的异常处理就生成如下源文件 Hello.java:
public class Hello
{
static
{
try
{
// 此处即为本地方法所在链接库名
System.loadLibrary("hello");
}
catch(UnsatisfiedLinkError e)
{
System.err.println( "Cannot load hello library:\n " +
e.toString() );
}
}
public Hello()
{
}
// 声明的本地方法
public native void SayHello(String strName);
}
|
编译后生成 Hello.class 文件。
第二步,生成本地链接库。具体过程如下:
1. 要为以上定义的类生成 Java 本地接口头文件,需使用 javah,Java 编译器的 javah 功能将根据 Hello 类生成必要的声明,此命令将生成 Hello.h 文件,我们在共享库的代码中要包含它,javah 不使默认内部命令,需要指明路径,它在 JDK 的 bin 目录下,在我的 Linux 环境下命令如下:
这里所谓的bin目录要特别注意,是指workspace中 编译 ******.java 所生产 ****.class 所在的bin目录
如上图所示 bin目录
/home/jbuilder/jdk1.3.1/bin/javah Hello
生成的 Hello.h 文件 内容如下所示:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Hello */
#ifndef _Included_Hello
#define _Included_Hello
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: Hello
* Method: SayHello
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_Hello_SayHello
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
|
2. 在与 Hello.h 相同的路径下创建一个 CPP 文件 Hello.cpp。内容如下:
#include "Hello.h"
#include <stdio.h>
// 与 Hello.h 中函数声明相同
JNIEXPORT void JNICALL Java_Hello_SayHello (JNIEnv * env, jobject arg, jstring instring)
{
// 从 instring 字符串取得指向字符串 UTF 编码的指针
const jbyte *str =
(const jbyte *)env->GetStringUTFChars( instring, JNI_FALSE );
printf("Hello,%s\n",str);
// 通知虚拟机本地代码不再需要通过 str 访问 Java 字符串。
env->ReleaseStringUTFChars( instring, (const char *)str );
return;
}
|
所有的 JNI 调用都使用了 JNIEnv * 类型的指针,习惯上在 CPP 文件中将这个变量定义为 evn,它是任意一个本地方法的第一个参数。env 指针指向一个函数指针表,在 VC 中可以直接用"->"操作符访问其中的函数。
jobject 指向在此 Java 代码中实例化的 Java 对象 LocalFunction 的一个句柄,相当于 this 指针。
后续的参数就是本地调用中有 Java 程序传进的参数,本例中只有一个 String 型参数。 对于字符串型参数,因为在本地代码中不能直接读取 Java 字符串,而必须将其转换为 C /C++ 字符串或 Unicode。以下是三个我们经常会用到的字符串类型处理的函数:
const char* GetStringUTFChars(jstring string,jboolean* isCopy)
返回指向字符串 UTF 编码的指针,如果不能创建这个字符数组,返回 null。这个指针在调用 ReleaseStringUTFChar() 函数之前一直有效。
参数:string Java 字符串对象
isCopy 如果进行拷贝,指向以 JNI_TRUE 填充的 jboolean, 否则指向以 JNI_FALSE 填充的 jboolean。
void ReleaseStringUTFChars(jstring str, const char* chars)
通知虚拟机本地代码不再需要通过 chars 访问 Java 字符串。
参数:string Java 字符串对象
chars 由 GetStringChars 返回的指针
jstring NewStringUTF(const char *utf)
返回一个新的 Java 字符串并将 utf 内容拷贝入新串,如果不能创建字符串对象,
返回 null。通常在反值类型为 string 型时用到。
参数:utf UTF 编码的字符串指针
对于数值型参数,在 C/C++ 中可直接使用,其字节宽度如下所示:
Java | C/C++ | 字节数 |
boolean | jboolean | 1 |
byte | jbyte | 1 |
char | jchar | 2 |
short | jshort | 2 |
int | jint | 4 |
long | jlong | 8 |
float | jfloat | 4 |
double | jdouble | 8 |
对于数组型参数,
Java | C/C++ |
boolean[ ] | JbooleanArray |
byte[ ] | JbyteArray |
char[ ] | JcharArray |
short[ ] | JshortArray |
int[ ] | JintArray |
long[ ] | JlongArray |
float[ ] | JfloatArray |
double[ ] | JdoubleArray |
对于上述类型数组,有一组函数与其对应。以下函数中 Xxx 为对应类型。
xxx * GetXxxArrayElements(xxxArray array, jboolean *isCopy)
产生一个指向 Java 数组元素的 C 指针。不再需要时,需将此指针传给 ReleaseXxxArrayElemes。
参数:array 数组对象
isCopy 如果进行拷贝,指向以 JNI_TRUE 填充的 jboolean, 否则指向以 JNI_FALSE 填充的 jboolean。
例如: jboolean * GetBooleanArrayElements(jbooleanArray array, jboolean *isCopy)
void ReleaseXxxArrayElements(xxxArray array,xxx *elems, jint mode)
通知虚拟机不再需要从 GetXxxArrayElements 得到的指针。
参数:array 数组对象
elems 不再需要的指向数组元素的指针
mode 0 =在更新数组元素后释放 elems 缓冲器
JNI_COMMIT =在更新数组元素后不释放 elems 缓冲器
JNI_ABORT =不更新数组元素释放 elems 缓冲器
例如:void ReleaseBooleanArrayElements(jbooleanArray array,jboolean *elems, jint mode)
xxxArray NewXxxArray(jsize len)
产生一个新的数组,通常在反值类型为数组型时用到
参数:len 数组中元素的个数。
例如:jbooleanArray NewBooleanArray(jsize len)
3 .编译生成共享库。
使用 GCC 时 , 必须通知编译器在何处查找此 Java 本地方法的支持文件,并且显式通知编译器生成位置无关的代码,在我的环境中按如下过程编译:
gcc -I/home/jbuilder/jdk1.3.1/include
-I/home/jbuilder/jdk1.3.1/include/linux -fPIC -c Hello.c
|
生成 Hello.o
gcc -shared -Wl,-soname,libhello.so.1 -o libhello.so.1.0 Hello.o
|
生成 libhello.so.1.0
接下来将生成的共享库拷贝为标准文件名
cp libhello.so.1.0 libhello.so
最后通知动态链接程序此共享文件的路径。
export LD_LIBRARY_PATH=`pwd`:$LD_LIBRARY_PATH
4 .编写一个简单的 Java 程序来测试我们的本地方法。
将如下源码存为 ToSay.java:
import Hello;
import java.util.*;
public class ToSay
{
public static void main(String argv[])
{
ToSay say = new ToSay();
}
public ToSay()
{
Hello h = new Hello();
// 调用本地方法向 John 问好
h.SayHello("John");
}
}
|
用 javac 编译 ToSay.java,生成 ToSay.class
向执行普通 Java 程序一样使用 java ToSay,我们会看到在屏幕上出现 Hello,John。
到这里我们就将整个的本地调用编写过程大概浏览了一遍。
回页首
应用中注意事项
1 . 如果可以通过 TCP/IP 实现 Java 代码与本地 C/C++ 代码的交互工作,那么最好不使用以上提到的 JNI 的方式,因为一次 JNI 调用非常耗时,大概要花 0.5 ~ 1 个毫秒。
2 . 在一个 Applet 应用中,不要使用 JNI。因为在 applet 中可能引发安全异常。
3 . 将所有本地方法都封装在单个类中,这个类调用单个 DLL。对于每种目标操作系统,都可以用特定于适当平台的版本替换这个 DLL。这样就可以将本地代码的影响减至最小,并有助于将以后所需的移植问题包含在内。
4 . 本地方法要简单。尽量将生成的 DLL 对任何第三方运行时 DLL 的依赖减到最小。使本地方法尽量独立,以将加载 DLL 和应用程序所需的开销减到最小。如果必须要运行时 DLL,则应随应用程序一起提供它们。
5 . 本地代码运行时,没有有效地防数组越界错误、错误指针引用带来的间接错误等。所以必须保证保证本地代码的稳定性,因为,丝毫的错误都可能导致 Java 虚拟机崩溃。
javah生成.h文件时提示“找不到类”的解决方法
当用javah为.clsaa文件生成.h文件的时候提示错误:
解决方法:
JNI是Java Native Interface的缩写,JNI是一种机制,有了它就可以在java程序中调用其他native代码,或者使native代码调用java层的代码。也就是说,有了JNI我们可以使Android项目中,java层与native层各自发挥所长并相互配合。
JAVA
| |
JNI
| |
NATIVE
JNI相对与native层来说是一个接口,java层的程序想访问native层,必须通过JNI,反过来也一样。
1,如何告诉VM(虚拟机)java层需要调用native层的哪些libs?
我们知道java程序是运行在VM上的,而Native层的libs则不然。所以为了让java层能访问native层的libs,必须得告诉VM要使用哪些native层的libs。下面看一段代码
可以看到上面的代码中,在MediaPlayer类中有一段static块包围起来的代码,其中System.loadLibrary("media_jni")就是告诉VM去加载libmedia_jni.so这个动态库,那么这个动态库什么时候被加载呢?因为static语句块的原因,所以在MediaPlayer第一次实例化的时候就会被加载了。这段代码中,我们还看到了一个函数native_init(),该函数被申明为native型,就是告诉VM该函数由native层来实现。
2,如何做到java层到native层的映射。
事实上我想表达的意思是,如何完成java层的代码到native层代码的映射,例如上面的代码中有一个native函数native_init(),那么如何使这个函数映射到一个由C/C++(或者其他语言)实现的具体函数呢?
当VM执行到System.loadLibrary()的时候就会去执行native libs中的JNI_OnLoad(JavaVM* vm, void* reserved)函数,因为JNI_OnLoad函数是从java层进入native层第一个调用的方法,所以可以在JNI_OnLoad函数中完成一些native层组件的初始化工作,同时更加重要的是,通常在JNI_jint JNI_OnLoad(JavaVM* vm, void* reserved)函数中会注册java层的native方法。下面看一段代码:
上面这段代码的JNI_OnLoad(JavaVM* vm, void* reserved)函数实现与libmedia_jni.so库中。上面的代码中调用了一些形如register_android_media_MediaPlayer(env)的函数,这些函数的作用是注册native method。我们来看看函数register_android_media_MediaPlayer(env)的实现。
最终jniRegisterNativeMethods函数完成java标准的native函数的映射工作。下面我们来具体的看看上面这个函数中各个参数的意义。
a,JNIEnv* env:
JNI defines two key data structures, "JavaVM" and "JNIEnv". Both of these are essentiallypointers to pointers to function tables. (In the C++ version, they're classes with apointer to a function table and a member function for each JNI function that indirects throughthe table.) The JavaVM provides the "invocation interface" functions,which allow you to create and destroy a JavaVM. In theory you can have multiple JavaVMs per process,but Android only allows one.
The JNIEnv provides most of the JNI functions. Your native functions all receive a JNIEnv asthe first argument.
The JNIEnv is used for thread-local storage. For this reason, you cannot share a JNIEnv between threads.If a piece of code has no other way to get its JNIEnv, you should sharethe JavaVM, and useGetEnv
to discover the thread's JNIEnv. (Assuming it has one; see AttachCurrentThread
below.)
b,char* className,这个没什么好说的,java空间中类名,其中包含了包名。
c,JNINativeMethod* gMethods,传递进去的是一个JNINativeMethod类型的指针gMethods,gMethods指向一个JNINativeMethod数组,我们先看看JNINativeMethod这个结构体。
再来看看gMethods数组
当VM载入libxxx_jni.so这个库时,就会呼叫JNI_OnLoad()函数。在JNI_OnLoad()中注册本地函数,继续调用到AndroidRuntime::registerNativeMethods(),该函数向VM(即AndroidRuntime)注册gMethods[]数组中包含的本地函数了。AndroidRuntime::registerNativeMethods()起到了以下两个作用:
1,registerNativeMethods()函数使得java空间中的Native函数更加容易的找到对应的本地函数。(通过gMethods[]中的函数指针)
2,可以在执行期间进行本地函数的替换。因为gMethods[]数组是一个<java中函数名字,本地函数指针>的对应表,所以可以在程序的执行过程中,多次呼叫registerNativeMethods()函数来更换本地函数的指针,提高程序的弹性。
函数签名:
在JNINativeMethod的结构体中,有一个描述函数的参数和返回值的签名字段,它是java中对应函数的签名信息,由参数类型和返回值类型共同组成。这个函数签名信息的作用是什么呢?
由于java支持函数重载,也就是说,可以定义同名但不同参数的函数。然而仅仅根据函数名是没法找到具体函数的。为了解决这个问题,JNI技术中就将参数类型和返回值类型的组合作为一个函数的签名信息,有了签名信息和函数名,就能顺利的找到java中的函数了。
JNI规范定义的函数签名信息格式如下:
(参数1类型标示参数2类型标示......参数n类型标示)返回值类型标示
实际上这些字符是与函数的参数类型一一对应的。
“()” 中的字符表示参数,后面的则代表返回值。例如”()V” 就表示 void Func();
“(II)V” 表示 void Func(int, int);
值得注意的一点是,当参数类型是引用数据类型时,
其格式是“L包名;”其中包名中的“.”换成“/”,
所以在上面的例子中(Ljava/lang/String;Ljava/lang/String;)V 表示 void Func(String,String);
如果 JAVA 函数位于一个嵌入类,则用$作为类名间的分隔符。
例如 “(Ljava/lang/String;Landroid/os/FileUtils$FileStatus;)Z”
V |
void |
void |
Z |
jboolean |
boolean |
I |
jint |
int |
J |
jlong |
long |
D |
jdouble |
double |
F |
jfloat |
float |
B |
jbyte |
byte |
C |
jchar |
char |
S |
jshort |
short |
|
||
[I |
jintArray |
int[] |
[F |
jfloatArray |
float[] |
[B |
jbyteArray |
byte[] |
[C |
jcharArray |
char[] |
[S |
jshortArray |
short[] |
[D |
jdoubleArray |
double[] |
[J |
jlongArray |
long[] |
[Z |
jbooleanArray |
Boolean[] |
数据类型转换:
在java层调用native函数传递到JNI层的参数,JNI层会做一些特殊处理,我们知道java数据类型分为基本数据类型和引用数据类型两种,JNI层也是区别对待的。下表示出了java数据类型—>native类型的转换。
其中在java数据类型中,除了java中基本数据类型和数组,Class,String,Throwable,其余所有的java对象的数据类型在JNI中用jobject表示。下面来看一段代码
从上面的代码可以看出来,java中的数据类型Parcel在JNI层对应的数据类型为jobejct,在JNI层的对应函数中,我们看到相对java层的native函数来说,多了两个参数JNIEnv *env ,jobject this。第二个参数jobject代表了java层的MediaPlayer对象,它表示在哪个MediaPlayer对象上调用的native_invoke。如果java层是static函数,那么这个参数将是jclass,表示是在调用那个java Class的静态函数。
还记得前面我们说过,java层和JNI层应该是可以互相交互,我们通过java层中的native函数可以进入到JNI层,那么JNI层的代码能不能操作java层中函数呢?当然可以,通过JNIEnv
JNIEnv再度解析
先来看看两个函数原型
结合前面的知识来看,JNIEnv是一个与线程相关的代表JNI环境的结构体。JNIEnv实际上提供了一些JNI系统函数。通过这些系统函数可以调用java层中的函数或者操作jobect。下面我看一段函数
可以看到上面的代码中,先找到java层中MediaScannerClinet类在JNI层中对应的jclass实例(通过FindClass)。然后拿到MediaScannerclient类中所需要用到函数的函数函数id(通过GetMethodID)。接着通过JNIEnv调用CallXXXMethod函数并且把对应的jobject,jMethodID还有对应的参数传递进去,这样的通过CallXXXMethod就完成了JNI层向java层的调用。这里要注意一点的是这里JNI层中调用的方法实际上是java中对象的成员函数,如果要调用static函数可以使用CallStaticXXXMethod。这种机制有利于native层回调java代码完成相应操作。
上面讲述了如下在JNI层中去调用java层的代码,那么理所当然的应该可以在JNI层中访问或者修改java层中某对象的成员变量的值。我们通过JNIEnv中的GetFieldID()函数来得到java中对象的某个域的id。看下面的具体代码
获得jfieldID之后呢,我们就可以在JNI层之间来访问和操作java层的field的值了,方法如下
注意这里的NativeType值得是jobject,jboolean等等。
现在我们看到有了JNIEnv,我们可以很轻松的操作jobject所代表的java层中的实际的对象了。
jstring介绍
之所以要把jstring单独拿出来说正是由于它的特殊性。java中String类型也是一个引用类型,但是JNI中并没有用jobject来与之对应,JNI中单独创建了一个jstring类型来表示java中的String类型。显然java中的String不能和C++中的String等同起来,那么怎么操作jstring呢?方法很多下面看几个简单的方法
1,调用JNIEnv的NewStringUTF将根据Native的一个UTF-8字符串得到一个jstring对象。只有这样才能让一个C++中String在JNI中使用。
2,调用JNIEnv的GetStringChars函数(将得到一个Unicode字符串)和GetStringUTFChars函数(将得到一个UTF-8字符串),他们可以将java String对象转换诚本地字符串。下面我们来看段事例代码。
GetStringUTFChars()函数将jstring类型转换成一个UTF-8本地字符串,另外如果代码中调用了上面的几个函数,则在做完相关工作后,要调用ReleaseStringChars函数或者ReleaseStringUTFChars函数来释放资源。看下面的代码
可以看到GetStringUTFChars与下面的ReleaseStringUTFChars对应。
参考: http://blog.csdn.net/mci2004/article/details/7211678
http://blog.csdn.net/mci2004/article/details/7219140
声明:本文转载自android开发网,http://www.android123.com.cn/androidkaifa/683.html
有关Android JNI开发中比较强大和有用的功能就是从JNI层创建、构造Java的类或执行Java层的方法获取属性等操作。
一、类的相关操作
1. jclass FindClass(JNIEnv *env, const char *name); 查找类
该函数可能做过Java开发的不会陌生,这个是JNI层的实现,需要注意的是第二个参数为const char*类型的,我们如果从Java从层传入unicode编码的jstring类型需要使用GetStringUTFChars函数转换成utf8的const char*,如果成功返回这个Java类的对象jclass,相关的异常可能有
(1. ClassFormatError 类的数据格式无效
(2. ClassCircularityError 该类或接口是自身的超类或超接口
(3. NoClassDefFoundError 没有找到指定名称的类或接口
(4. OOM内存不足错误,即OutOfMemoryError
2. jclass GetSuperclass(JNIEnv *env, jclass clazz); 获取父类或者说超类
该函数的第二个参数为jclass类,我们调用时传入的是子类,否则返回将是NULL
3. jboolean IsAssignableFrom(JNIEnv *env, jclass clazz1,jclass clazz2); 判断class1对象能否安全的强制转换为class2对象
如果可以将返回 JNI_TRUE,JNI_TRUE的定义值为1,否则返回JNI_FALSE即0 ,这里Android123详细说明下哪些情况可能返回真:
(1 这两个类参数引用同一个 Java 类
(2 第一个类是第二个类的子类
(3 第二个类是第一个类的某个接口
4. jclass GetObjectClass(JNIEnv *env, jobject obj); 通过对象获取这个类
该函数比较简单,唯一注意的是对象不能为NULL,否则获取的class肯定返回也为NULL。
5. jboolean IsInstanceOf(JNIEnv *env, jobject obj,jclass clazz); 判断对象是否为某个类的实例
这个函数是JNI层的实现,相信大家都不陌生,Android开发网提醒大家需要注意的是返回值可能产生异议,就是如果传入的第二个参数为NULL对象,NULL对象可以强制转换为各种类,所以这种情况也将会返回JNI_TRUE,所以一定判断传入的对象是否为空。
6. jboolean IsSameObject(JNIEnv *env, jobject ref1,jobject ref2); 判断两个对象是否引用同一个类
需要注意的是如果两个对象均为空,返回的值也会是JNI_TRUE所以使用时判断对象为空。
二、调用Java方法
首先说下有关签名sig相关的比如 "Ljava/lang/String;"
1. jmethodID GetMethodID(JNIEnv *env, jclass clazz,const char *name, const char *sig); 获取一个Java方法的ID
这个函数将返回非静态类或接口实例方法的方法 ID。这个方法可以是某个clazz 的超类中定义,也可从clazz 继承,最后一个参数为签名,最后两个参数是const char*类型,是utf8类型。需要注意的是Android123提醒大家执行GetMethodID()函数将导致未初始化的类初始化,如果要获得构造函数的方法ID,使用 <init> 作为方法名,同时将 void (V) 作为返回类型,如果找不到指定的ID将返回NULL,同时异常可能有:
(1 NoSuchMethodError 找不到指定的Java方法。
(2 ExceptionInInitializerError 如果由于异常而导致类初始化程序失败
(3 OutOfMemoryError 内存不足
2 . NativeType CallXXXMethod (JNIEnv *env, jobject obj,jmethodID methodID, va_list args); 调用XXX类型的Java方法
执行Java类中某个方法,需要注意的是这个里的java类是非静态的,由于Java的方法的类型比较多,所以该函数可能有以下几种形式,如CallObjectMethod,CallBooleanMethod,CallByteMethod,CallCharMethod,CallShortMethod,CallIntMethod,CallLongMethod,CallFloatMethod,CallDoubleMethod和CallVoidMethod,需要注意的是,该函数的第三个参数为通过GetMethodID函数获取的方法ID,最后一个参数为这个方法的参数表,最后的va_list宏可以通过搜索获取具体的使用方法,这里Android开发网不再赘述。
3.NativeType CallNonvirtualXXXMethod (JNIEnv *env, jobject obj,jclass clazz, jmethodID methodID, jvalue *args);
CallNonvirtualXXXMethod函数和上面的CallXXXMethod 不同之处是多了一个jclass参数,CallXXXMethod是根据对象来调用方法,而CallNonvirtualXXXMethod是根据类的实例调用,区别在这点。
上面的三个均为非静态类的获取,执行调用,需要实例化这个类才可以执行,下面的为静态调用。
4. jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz,const char *name, const char *sig);
5. NativeType CallStaticXXXMethod(JNIEnv *env, jclass clazz,jmethodID methodID, ...);
三、访问Java对象的域
Java对象的域或者说字段、属性(Field) 类似方法的执行
1. jfieldID GetFieldID(JNIEnv *env, jclass clazz,const char *name, const char *sig); 获取实例对象的域ID
需要注意的是,非静态的实例化后的对象,可能产生的异常有
(1 NoSuchFieldError 找不到指定的域
(2 ExceptionInInitializerError 因为异常而导致类初始化失败
(3 OutOfMemoryError内存不足。
2. NativeType GetXXXField(JNIEnv *env, jobject obj,jfieldID fieldID);
类似GetXXXMethod函数,可能有的类型有 GetObjectField,GetBooleanField,GetByteField,GetCharField,GetShortField,GetIntField,GetLongField,GetFloatField,GetDoubleField。
3. void SetXXXField(JNIEnv *env, jobject obj, jfieldID fieldID,NativeType value);
Java的域可以赋值的,可能有的类型有 SetObjectField,SetBooleanField,SetByteField,SetCharField,SetShortField,SetIntField,SetLongField,SetFloatField,SetDoubleField。
上面3种情况均为非静态对象的域,对于不需要实例化对象的域,可以直接使用下面的。
4. jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz,const char *name, const char *sig);
5. NativeType GetStaticXXXField(JNIEnv *env, jclass clazz,jfieldID fieldID);
6. void SetStaticXXXField(JNIEnv *env, jclass clazz,jfieldID fieldID, NativeType value);
四、实例代码,Android123给网友准备了一个例子,帮助大家实战Android JNI开发,大家可以移植到Android NDK环境中执行,网友可以访问 Android JNI开发代码 (Android JNI实例代码(一))
最后有关Android JNI最后的终极内容,Android开发网主要说明下JVM和JNI的全局引用相关内容,比如本地全局引用LocalGlobalRef,弱全局引用WeakGlobalRef,JNI中线程处理的高级方法比如AttachCurrentThread,以及JNI中的NIO的相关特性将在明天继续讲解,更多的有关Android平台NDK开发内容可以查看我们 Android NDK开发技巧系列文章。
from:https://thenewcircle.com/s/post/1292/jni_reference_example
Just when I thought that the world had finally accepted "pure" Java, along came Android, with its nifty Dalvik VM and a lot of dependencies onJNI, adding native code back into the mix.
For those that don't know, Java Native Interface (JNI) enables code written in Java to access (bind to) code written in C/C++ (and vice-versa) thereby allowing developers to gain access to low-level OS APIs, reuse legacy code, and possibly even boost the execution performance.
Since we talk quite a bit about JNI in our Android Internals training course, I thought I should share some of our material focusing on this topic. I hope you find it useful.
In this module, we’ll explore the following topics:
JNI Components
JNI By Example
Native Method Arguments
Primitive Type Mapping
Reference Type Mapping
Global and Local References
Using Strings
Arrays
Reflection
Registering Native Methods On Load
Exceptions
JNI is an interface that allows Java to interact with code written in another language
Motivation for JNI:
JNI code is not portable! |
JNI can also be used to invoke Java code from within natively-written applications - such as those written in C/C++. In fact, the java command-line utility is an example of one such application, that launches Java code in a Java Virtual Machine. |
javah - JDK tool that builds C-style header files from a given Java class that includesnative methods
Adapts Java method signatures to native function prototypes
jni.h - C/C+ header file included with the JDK that maps Java types to their native counterparts
javah automatically includes this file in the application header files
We start by creating a Java class with one or more native methods
package com.marakana.jniexamples; public class Hello { public static native void sayHi(String who, int times); // static { System.loadLibrary("hello"); // } public static void main(String[] args) { sayHi(args[0], Integer.parseInt(args[1])); // } }
The method sayHi(String, int) will be implemented in C/C+ in separate files, which will be compiled into a shared library. | |
Load the shared library by its logical name. The actual name is system-dependent:libhello.so (on Linux/Unix),hello.dll (on Windows), andlibhello.jnilib (Mac OSX). | |
Here we call our native method as a regular Java method. |
Compile the Java code
$ mkdir -p bin $ javac -d bin/ src/com/marakana/jniexamples/Hello.java
Using the javah tool, we generate the C header file from the compiledcom.marakana.jniexamples.Hello class:
$ mkdir -p jni $ javah -jni -classpath bin -d jni com.marakana.jniexamples.Hello
Observe the generated C header file:
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_marakana_jniexamples_Hello */ #ifndef _Included_com_marakana_jniexamples_Hello #define _Included_com_marakana_jniexamples_Hello #ifdef __cplusplus extern "C" { #endif /* * Class: com_marakana_jniexamples_Hello * Method: sayHi * Signature: (Ljava/lang/String;I)V */ JNIEXPORT void JNICALL Java_com_marakana_jniexamples_Hello_sayHi (JNIEnv *, jclass, jstring, jint); #ifdef __cplusplus } #endif #endif
Method names resolve to C functions based on a pre-defined naming strategy:the prefixJava_, followed by a mangled fully-qualified class name, followed by an underscore ("_") separator, followed by a mangled method name. For overloaded native methods, two underscores ("__") followed by the mangled argument signature. |
Provide the C implementation:
#include "com_marakana_jniexamples_Hello.h" JNIEXPORT void JNICALL Java_com_marakana_jniexamples_Hello_sayHi (JNIEnv *env, jclass clazz, jstring who, jint times) { const char *name = (*env)->GetStringUTFChars(env, who, NULL); if (name != NULL) { jint i; for (i = 0; i < times; i++) { printf("Hello %s\n", name); } (*env)->ReleaseStringUTFChars(env, who, name); } }
Most of the time, we cannot just use Java data types directly in C. For example, we have to convertjava.lang.String tochar * before we can effectively use it in C. |
This code assumes: #define NULL ((void *) 0) |
Compile the shared library
$ mkdir -p libs $ gcc -o libs/libhello.jnilib -lc -shared \ -I/System/Library/Frameworks/JavaVM.framework/Headers \ jni/com_marakana_jniexamples_Hello.c $ file libs/libhello.jnilib libs/libhello.jnilib: Mach-O 64-bit dynamically linked shared library x86_64
On Unix/Linux, compile as: gcc -o libs/libhello.so -lc -shared -fPIC -I$JAVA_HOME/include jni/com_marakana_jniexamples_Hello.c |
Run our code
$ java -Djava.library.path=libs -classpath bin com.marakana.jniexamples.Hello Student 5 Hello Student Hello Student Hello Student Hello Student Hello Student
Instead of specifying -Djava.library.path=libs, we could have preceded ourjava command withexport LD_LIBRARY_PATH=libs. |
Common mistakes resulting in java.lang.UnsatisfiedLinkError usually come from incorrect naming of the shared library (O/S-dependent), the library not being in the search path, or wrong library being loaded by Java code. |
A C/C++ implementation of a native Java method accepts the following function arguments:
JNIEnv *env, an interface pointer (pointer to a pointer) to afunction table, where each entry in the table is a pointer to a function that enables Java-to-C/C++ integration (e.g. type conversion)
The second argument varies depending on whether the native method is a static method or an instance (i.e. non-static) method:
In case of instance methods, the second argument is a jobject obj, which is a reference to the object on which the method is invoked
In case of static methods, the second is a jclass clazz, which is a reference to the class in which the method is defined
The remaining arguments correspond to regular Java method arguments (subject to the mapping described below)
The native function can pass its result back to Java via the return value (or returnvoid)
Java primitives have well-known size and are signed by default
C/C++ primitives vary in size, depending on the platform (e.g. the size of thelong depends on the size of the word)
To provide inter-operability with C/C++, jni.h defines the following mappings:
Java Language Type | Native Type | Description | typedef (C99) | typedef (otherwise) |
---|---|---|---|---|
boolean |
jboolean |
unsigned 8 bits |
uint8_t |
unsigned char |
byte |
jbyte |
signed 8 bits |
int8_t |
signed char |
char |
jchar |
unsigned 16 bits |
uint16_t |
unsigned short |
short |
jshort |
signed 16 bits |
int16_t |
short |
int |
jint |
signed 32 bits |
int32_t |
int |
long |
jlong |
signed 64 bits |
int64_t |
long long |
float |
jfloat |
32-bit IEEE 754 |
float |
float |
double |
jdouble |
64-bit IEEE 754 |
double |
double |
void |
void |
N/A |
N/A |
N/A |
N/A |
jsize |
used to describe cardinal indices and sizes |
jint |
jint |
Because booleans are treated as unsigned bytes, the following definitions are also useful
Java Boolean Value | Native Boolean Type | Definition |
---|---|---|
false |
JNI_FALSE |
#define JNI_FALSE 0 |
true |
JNI_TRUE |
#define JNI_TRUE 1 |
Primitive data types are always copied between Java and native code
The JNI includes a number of pre-defined reference types (passed as opaque references in C) that correspond to different kinds of Java object types:
Java Language Type | Native Type | typedef in C |
---|---|---|
java.lang.Object |
jobject |
void* |
java.lang.Class |
jclass |
jobject |
java.lang.Throwable |
jthrowable |
jobject |
java.lang.String |
jstring |
jobject |
java.lang.ref.WeakReference |
jweak |
jobject |
N/A |
jarray |
jobject |
java.lang.Object[] |
jobjectArray |
jarray |
boolean[] |
jbooleanArray |
jarray |
byte[] |
jbyteArray |
jarray |
char[] |
jcharArray |
jarray |
short[] |
jshortArray |
jarray |
int[] |
jintArray |
jarray |
long[] |
jlongArray |
jarray |
float[] |
jfloatArray |
jarray |
double[] |
jdoubleArray |
jarray |
A note about reference types in C++
These reference types in C++ are defined as proper classes: class _jobject {}; class _jclass : public _jobject {}; class _jstring : public _jobject {}; class _jarray : public _jobject {}; class _jobjectArray : public _jarray {}; class _jbooleanArray : public _jarray {}; class _jbyteArray : public _jarray {}; class _jcharArray : public _jarray {}; class _jshortArray : public _jarray {}; class _jintArray : public _jarray {}; class _jlongArray : public _jarray {}; class _jfloatArray : public _jarray {}; class _jdoubleArray : public _jarray {}; class _jthrowable : public _jobject {}; typedef _jobject* jobject; typedef _jclass* jclass; typedef _jstring* jstring; typedef _jarray* jarray; typedef _jobjectArray* jobjectArray; typedef _jbooleanArray* jbooleanArray; typedef _jbyteArray* jbyteArray; typedef _jcharArray* jcharArray; typedef _jshortArray* jshortArray; typedef _jintArray* jintArray; typedef _jlongArray* jlongArray; typedef _jfloatArray* jfloatArray; typedef _jdoubleArray* jdoubleArray; typedef _jthrowable* jthrowable; typedef _jobject* jweak; |
A tip about
NULL in Android NDK
Android’s native development kit (NDK) does not define NULL in its jni.h, so the following definition can be useful when working with pointers and reference types: #define NULL ((void *) 0) |
Opaque references are C pointer types that refer to internal data structures in the JVM. It is an error to try to dereference opaque references and try to use them directly. |
Unlike primitives, arbitrary Java objects are always passed by reference
The VM must keep track of all objects that have been passed to the native code, so that these objects are not freed by the GC (i.e. these references are considered objectroots while in use)
Consequently, the native code must have a way to tell the VM that it no longer needs references to objects
Additionally, the GC must be able to relocate an object referred to by native code (in the case of a copying GC), which means that these references must not bedereferenced by the native code
We will access the fields/methods on these references via JNI accessor functions
All objects passed to native code and returned from JNI functions are considered local references
Automatically "freed" after the native function call returns
The VM creates a table of references before every function call, and frees it when the call completes
Consequently, the native function calls are more expensive than regular method calls
Also possible to explicitly free using:
void DeleteLocalRef(JNIEnv *env, jobject localRef);
In practice, we only really want to use DeleteLocalRef(JNIEnv *, jobject) if we know we are not going to need the reference in the rest of the function body, say before we start executing code that may take a long time. This allows the referenced memory to be freed by GC, assuming nobody else is using it. |
Only valid in the thread in which they are created
If we end up using more than 16 local references (default) within a native function scope, we can ask the VM for more:
jint EnsureLocalCapacity(JNIEnv *env, jint capacity); jint PushLocalFrame(JNIEnv *env, jint capacity); jobject PopLocalFrame(JNIEnv *env, jobject result);
To hold on to an object beyond a single native function call, we can convert a local reference to aglobal reference
jobject NewGlobalRef(JNIEnv *env, jobject obj);
Now we could save the result of NewGlobalRef(JNIEnv *, jobect) to a global/static variable and use it later, in another funciton call. |
Becomes our responsibility to explicitly delete the reference when no longer in use:
void DeleteGlobalRef(JNIEnv *env, jobject globalRef);
Also supported are the weak global references
jweak NewWeakGlobalRef(JNIEnv *env, jobject obj); void DeleteWeakGlobalRef(JNIEnv *env, jweak obj);
JNI accessor functions do not make a distinction between the different reference types
In C, a string is a pointer to a \0-terminated character array where each character is one byte
In Java, an instance of java.lang.String is an immutable object which wraps a character array, which itself is an object (i.e. it knows its length so it is not\0-terminated), and each character is represented in UTF16 astwo bytes
String Operations are provided byJNIEnv:
/* On Unicode (UTF-16) Characters */ jstring NewString(JNIEnv *env, const jchar *unicodeChars, jsize len); jsize GetStringLength(JNIEnv *env, jstring string); const jchar * GetStringChars(JNIEnv *env, jstring string, jboolean *isCopy); void ReleaseStringChars(JNIEnv *env, jstring string, const jchar *chars); void GetStringRegion(JNIEnv *env, jstring str, jsize start, jsize len, jchar *buf); /* On (modified) UTF-8 Characters */ jstring NewStringUTF(JNIEnv *env, const char *bytes); jsize GetStringUTFLength(JNIEnv *env, jstring string); const char * GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy); void ReleaseStringUTFChars(JNIEnv *env, jstring string, const char *utf); void GetStringUTFRegion(JNIEnv *env, jstring str, jsize start, jsize len, char *buf);
A note about
GetString[UTF]Chars(…) functions
The pointer resulting from GetString[UTF]Chars(…) is valid untilReleaseString[UTF]Chars(…) is called. If isCopy is not NULL, then*isCopy is set toJNI_TRUE if a copy is made. When*isCopy == JNI_FALSE, the returned string is adirect pointer to the characters in the originaljava.lang.String instance, which is then pinned in memory. The native code must ensurenot to modify the contents of the returned string, otherwise, it would be modifying the private data of theimmutablejava.lang.String object! Regardless of isCopy, we have to call ReleaseString[UTF]Chars(…) when we are done using the character array, either to free the memory (when*isCopy == JNI_TRUE) or to un-pin the original string in memory (when*isCopy == JNI_FALSE). |
A note about modified UTF-8 strings:
JNI’s UTF string functions (that work with char *) return/assume\0-terminated character arrays that are encoded as UTF-8 character sequences, except that if the stringcontains a \u0000 character, it is represented by a pair of bytes0xc0 0x80 (1100000010000000) instead of0x00. When working with regular ASCII characters, each character is represented by exactly one byte. |
Examples:
What NOT to do:
#include <stdio.h> #include "com_marakana_jniexamples_HelloName.h" JNIEXPORT void JNICALL Java_com_marakana_jniexamples_HelloName_sayHelloName(JNIEnv *env, jclass clazz, jstring name) { printf("Hello %s", name); /* */ }
This example would not work (would likely crash the VM) since the jstring type represents strings in the Java virtual machine. This is different from the C string type (char *). |
Convert Java string to C string:
#include <stdio.h> #include "com_marakana_jniexamples_HelloName.h" JNIEXPORT void JNICALL Java_com_marakana_jniexamples_HelloName_sayHelloName(JNIEnv *env, jclass clazz, jstring name){ const char *cName = (*env)->GetStringUTFChars(env, name, NULL); /* */ if (cName == NULL) { return; /* OutOfMemoryError already thrown */ } else { printf("Hello %s\n", cName); (*env)->ReleaseStringUTFChars(env, name, cName); /* */ } }
This returns a pointer to an array of bytes representing the string in modified UTF-8 encoding; or NULL if we ran out of memory, in which casejava.lang.OutOfMemoryError would also be thrown (upon returning back to the Java runtime). | |
Calling ReleaseStringUTFChars indicates that the native method no longer needs the UTF-8 string returned byGetStringUTFChars, thus the memory taken by the UTF-8 string can be freed. Failure to do this would result in a memory leak, which could ultimately lead to memory exhaustion. |
Convert C string to Java string:
#include <stdio.h> #include "com_marakana_jniexamples_GetName.h" JNIEXPORT jstring JNICALL Java_com_marakana_jniexamples_ReturnName_GetName(JNIEnv *env, jclass class) { char buf[20]; fgets(buf, sizeof(buf), stdin); return (*env)->NewStringUTF(env, buf); }
Print each character of a Java string
#include <stdio.h> #include "com_marakana_jniexamples_HelloName.h" JNIEXPORT void JNICALL Java_com_marakana_jniexamples_HelloName_sayHelloName(JNIEnv *env, jclass clazz, jstring name) { char buf[4]; jint i; jsize len = (*env)->GetStringUTFLength(env, name); fputs("Hello ", stdout); for (i = 0; i < len; i++) { (*env)->GetStringUTFRegion(env, name, i, 1, buf); putc(buf[0], stdout); /* assumes ASCII */ } putc('\n', stdout); }
As with Strings, Java arrays are different than C arrays; the former are objects (whichknow theirlength) whereas the latter are just pointers to memory addresses
So, int[] in Java is not the same as jint[] in C/C++
Instead, int[] in Java, becomes jintArray in C/C++
JNI provides functions for
Creating arrays:
jobjectArray NewObjectArray(JNIEnv *env, jsize length, jclass elementClass, jobject initialElement); jbooleanArray NewBooleanArray(JNIEnv *env, jsize length); jbyteArray NewByteArray(JNIEnv *env, jsize length); jcharArray NewCharArray(JNIEnv *env, jsize length); jshortArray NewShortArray(JNIEnv *env, jsize length); jintArray NewIntArray(JNIEnv *env, jsize length); jlongArray NewLongArray(JNIEnv *env, jsize length); jfloatArray NewFloatArray(JNIEnv *env, jsize length); jdoubleArray NewDoubleArray(JNIEnv *env, jsize length);
Getting the length of an array
jsize GetArrayLength(JNIEnv *env, jarray array);
Getting/Setting individual elements of object arrays
jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index); void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject value);
Converting Java arrays of primitives to C/C++ arrays:
jboolean* GetBooleanArrayElements(JNIEnv *env, jbooleanArray array, jboolean *isCopy); jbyte* GetByteArrayElements(JNIEnv *env, jbyteArray array, jboolean *isCopy); jchar* GetCharArrayElements(JNIEnv *env, jcharArray array, jboolean *isCopy); jshort* GetShortArrayElements(JNIEnv *env, jshortArray array, jboolean *isCopy); jint* GetIntArrayElements(JNIEnv *env, jintArray array, jboolean *isCopy); jlong* GetLongArrayElements(JNIEnv *env, jlongArray array, jboolean *isCopy); jfloat* GetFloatArrayElements(JNIEnv *env, jfloatArray array, jboolean *isCopy); jdouble* GetDoubleArrayElements(JNIEnv *env, jdoubleArray array, jboolean *isCopy); void ReleaseBooleanArrayElements(JNIEnv *env, jbooleanArray array, jboolean *elems, jint mode); void ReleaseByteArrayElements(JNIEnv *env, jbyteArray array, jbyte *elems, jint mode); void ReleaseCharArrayElements(JNIEnv *env, jcharArray array, jchar *elems, jint mode); void ReleaseShortArrayElements(JNIEnv *env, jshortArray array, jshort *elems, jint mode); void ReleaseIntArrayElements(JNIEnv *env, jintArray array, jint *elems, jint mode); void ReleaseLongArrayElements(JNIEnv *env, jlongArray array, jlong *elems, jint mode); void ReleaseFloatArrayElements(JNIEnv *env, jfloatArray array, jfloat *elems, jint mode); void ReleaseDoubleArrayElements(JNIEnv *env, jdoubleArray array, jdouble *elems, jint mode);
Copying Java primitive arrays to and from C/C++ arrays:
void GetBooleanArrayRegion(JNIEnv *env, jbooleanArray array, jsize start, jsize len, jboolean *buf); void GetByteArrayRegion(JNIEnv *env, jbyteArray array, jsize start, jsize len, jbyte *buf); void GetCharArrayRegion(JNIEnv *env, jcharArray array, jsize start, jsize len, jchar *buf); void GetShortArrayRegion(JNIEnv *env, jshortArray array, jsize start, jsize len, jshort *buf); void GetIntArrayRegion(JNIEnv *env, jintArray array, jsize start, jsize len, jint *buf); void GetLongArrayRegion(JNIEnv *env, jlongArray array, jsize start, jsize len, jlong *buf); void GetFloatArrayRegion(JNIEnv *env, jfloatArray array, jsize start, jsize len, jfloat *buf); void GetDoubleArrayRegion(JNIEnv *env, jdoubleArray array, jsize start, jsize len, jdouble *buf); void SetBooleanArrayRegion(JNIEnv *env, jbooleanArray array, jsize start, jsize len, jboolean *buf); void SetByteArrayRegion(JNIEnv *env, jbyteArray array, jsize start, jsize len, jbyte *buf); void SetCharArrayRegion(JNIEnv *env, jcharArray array, jsize start, jsize len, jchar *buf); void SetShortArrayRegion(JNIEnv *env, jshortArray array, jsize start, jsize len, jshort *buf); void SetIntArrayRegion(JNIEnv *env, jintArray array, jsize start, jsize len, jint *buf); void SetLongArrayRegion(JNIEnv *env, jlongArray array, jsize start, jsize len, jlong *buf); void SetFloatArrayRegion(JNIEnv *env, jfloatArray array, jsize start, jsize len, jfloat *buf); void SetDoubleArrayRegion(JNIEnv *env, jdoubleArray array, jsize start, jsize len, jdouble *buf);
For example, to sum the contents of a Java int array in C, we could:
JNIEXPORT jint JNICALL Java_com_marakana_jniexamples_Math_sum(JNIEnv *env, jclass clazz, jintArray array) { jint *cArray = (*env)->GetIntArrayElements(env, array, NULL); if (cArray == NULL) { return 0; } else { jini len = (*env)->GetArrayLength(env, array); jint i; jint result = 0; for (i = 0; i < len; i++) { result += cArray[i]; } (*env)->ReleaseIntArrayElements(env, array, cArray, JNI_ABORT); return result; } }
Alternatively, we could also extract the individual elements:
JNIEXPORT jint JNICALL Java_com_marakana_jniexamples_Math_sum(JNIEnv *env, jclass clazz, jintArray array) { jint buf[1]; jini len = (*env)->GetArrayLength(env, array); jint i; jint result = 0; for (i = 0; i < len; i++) { (*env)->GetIntArrayRegion(env, array, i, 1, buf); result += buf[0]; } return result; }
To do the opposite, copy a C array to Java array, we would do:
JNIEXPORT jintArray JNICALL Java_com_marakana_jniexamples_Foo_getData(JNIEnv *env, jclass class) { jint cArray[10]; jsize len = sizeof(cArray); jintArray jArray = (*env)->NewIntArray(env, len); if (jArray != NULL) { jint i; /* "get" the data */ for (i = 0; i < len; i++) { cArray[i] = i; } (*env)->SetIntArrayRegion(env, jArray, 0, len, cArray); } return jArray; }
To avoid any chance of memory copying, we could also use direct memory buffers (java.nio.ByteBuffer):
jobject NewDirectByteBuffer(JNIEnv* env, void* address, jlong capacity); void* GetDirectBufferAddress(JNIEnv* env, jobject buf); jlong GetDirectBufferCapacity(JNIEnv* env, jobject buf);
For example
public class Foo { public static void main(String[] args) { … ByteBuffer buf = ByteBuffer.allocateDirect(1024); // populate buf processData(buf); … } public native static void processData(ByteBuffer buf); }
JNIEXPORT void JNICALL Java_com_marakana_jniexamples_Foo_processData(JNIEnv *env, jclass clazz, jobject buf) { char *cBuf = (*env)->GetDirectBufferAddress(env, buf); /* process cBuf from 0 to (*env)->GetDirectBufferCapacity(env, buf) */ }
A note about memory
The pointer resulting from GetTypeArrayElements(…) is valid untilReleaseTypeArrayElements(…) is called (unlessmode == JNI_COMMIT). If isCopy is not NULL, then*isCopy is set toJNI_TRUE if a copy is made. When*isCopy == JNI_FALSE, the returned array is adirect pointer to the elements of the Java array, which is then pinned in memory. Regardless of isCopy, we have to call ReleaseTypeArrayElements(…, int mode) when we are done using the native array, either to un-pin the Java array in memory when*isCopy == JNI_FALSE, or, when*isCopy == JNI_TRUE, to:
JNI also supports a critical version of these functions: void * GetPrimitiveArrayCritical(JNIEnv *env, jarray array, jboolean *isCopy); void ReleasePrimitiveArrayCritical(JNIEnv *env, jarray array, void *carray, jint mode); The function GetPrimitiveArrayCritical(…) is similar toGetTypeArrayElements(…), except that the VM is more likely to return the pointer to the primitive array (i.e.*isCopy == JNI_FALSE is more likely). However, this comes with some caveats. The native code betweenGetPrimitiveArrayCritical(…) andReleasePrimitiveArrayCritical(…) must not call other JNI functions, or make any system calls that may cause the current thread to block and wait for another Java thread. This is because the VM may temporarily suspend the garbage collection - in case it does not support memory pinning. |
JNI supports getting a reference to a Java class in native code:
jclass FindClass(JNIEnv *env, const char *name);
The name argument is a fully-qualified classname (with/ as the package separators)
/* load the java.lang.String class */ (*env)->FindClass(env, "java/lang/String");
For arrays, we use [Lclassname; format
/* load the java.lang.String[] class */ (*env)->FindClass(env, "[Ljava/lang/String;");
Given a reference to a class, we can find out its super class, as well as whether it is assignable from another class:
jclass GetSuperclass(JNIEnv *env, jclass clazz); jboolean IsAssignableFrom(JNIEnv *env, jclass clazz1, jclass clazz2);
Given a reference to an object, we can find out its class, as well as whether it is an instance of another class:
jclass GetObjectClass(JNIEnv *env, jobject obj); jboolean IsInstanceOf(JNIEnv *env, jobject obj, jclass clazz);
Once we have a class, JNI offers functions to lookup, get/set fields and invoke methods of that class - based on the following field and method signatures:
Type Signature | Java Type |
---|---|
Z |
boolean |
B |
byte |
C |
char |
S |
short |
I |
int |
J |
long |
F |
float |
D |
double |
Lfully-qualified-class; |
fully-qualified-class |
[type |
type[] |
(arg-types)ret-type |
method type |
For fields, JNI offers functions to:
Lookup a field ID based on its name/signature:
jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig); jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
Get/set the value of a field based on the field ID:
jobject (*GetStaticObjectField)(JNIEnv*, jclass, jfieldID); jboolean (*GetStaticBooleanField)(JNIEnv*, jclass, jfieldID); jbyte (*GetStaticByteField)(JNIEnv*, jclass, jfieldID); jchar (*GetStaticCharField)(JNIEnv*, jclass, jfieldID); jshort (*GetStaticShortField)(JNIEnv*, jclass, jfieldID); jint (*GetStaticIntField)(JNIEnv*, jclass, jfieldID); jlong (*GetStaticLongField)(JNIEnv*, jclass, jfieldID); jfloat (*GetStaticFloatField)(JNIEnv*, jclass, jfieldID); jdouble (*GetStaticDoubleField)(JNIEnv*, jclass, jfieldID); void (*SetStaticObjectField)(JNIEnv*, jclass, jfieldID, jobject); void (*SetStaticBooleanField)(JNIEnv*, jclass, jfieldID, jboolean); void (*SetStaticByteField)(JNIEnv*, jclass, jfieldID, jbyte); void (*SetStaticCharField)(JNIEnv*, jclass, jfieldID, jchar); void (*SetStaticShortField)(JNIEnv*, jclass, jfieldID, jshort); void (*SetStaticIntField)(JNIEnv*, jclass, jfieldID, jint); void (*SetStaticLongField)(JNIEnv*, jclass, jfieldID, jlong); void (*SetStaticFloatField)(JNIEnv*, jclass, jfieldID, jfloat); void (*SetStaticDoubleField)(JNIEnv*, jclass, jfieldID, jdouble); jobject (*GetObjectField)(JNIEnv*, jobject, jfieldID); jboolean (*GetBooleanField)(JNIEnv*, jobject, jfieldID); jbyte (*GetByteField)(JNIEnv*, jobject, jfieldID); jchar (*GetCharField)(JNIEnv*, jobject, jfieldID); jshort (*GetShortField)(JNIEnv*, jobject, jfieldID); jint (*GetIntField)(JNIEnv*, jobject, jfieldID); jlong (*GetLongField)(JNIEnv*, jobject, jfieldID); jfloat (*GetFloatField)(JNIEnv*, jobject, jfieldID); jdouble (*GetDoubleField)(JNIEnv*, jobject, jfieldID); void (*SetObjectField)(JNIEnv*, jobject, jfieldID, jobject); void (*SetBooleanField)(JNIEnv*, jobject, jfieldID, jboolean); void (*SetByteField)(JNIEnv*, jobject, jfieldID, jbyte); void (*SetCharField)(JNIEnv*, jobject, jfieldID, jchar); void (*SetShortField)(JNIEnv*, jobject, jfieldID, jshort); void (*SetIntField)(JNIEnv*, jobject, jfieldID, jint); void (*SetLongField)(JNIEnv*, jobject, jfieldID, jlong); void (*SetFloatField)(JNIEnv*, jobject, jfieldID, jfloat); void (*SetDoubleField)(JNIEnv*, jobject, jfieldID, jdouble);
For example:
public class Foo { private String bar; … public native void processBar(); }
JNIEXPORT void JNICALL Java_com_marakana_jniexamples_Foo_processBar(JNIEnv *env, jobject object) { /* Same as object.getClass() */ jclass clazz = (*env)->GetObjectClass(env, object); if (clazz != NULL) { /* cannot be null in this case */ /* Same as clazz.getField("bar") */ jfieldID field = (*env)->GetFieldID(env, clazz, "bar", "Ljava/lang/String;"); if (field != NULL) { /* make sure we got the field */ /* Same as field.get(object) */ jstring jString = (*env)->GetObjectField(env, object, field); if (jString != NULL) { /* Convert the value to a C (UTF-8) string */ const char *cString = (*env)->GetStringUTFChars(env, jString, NULL); if (cString == NULL) { return; /* Out of memory */ } printf("Value of \"bar\" before the change: \"%s\"\n", cString); (*env)->ReleaseStringUTFChars(env, jString, cString); } /* Create a new String */ jString = (*env)->NewStringUTF(env, "Bar2"); if (jString != NULL) { /* make sure we are not out of memory */ /* Same as field.set(object, jString) */ (*env)->SetObjectField(env, object, field, jString); } } } }
For methods, JNI offers functions to:
Lookup a method ID based on its name and signature:
jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig); jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
Invoke a method based on the method ID:
jobject (*CallStaticObjectMethod)(JNIEnv*, jclass, jmethodID, ...); jboolean (*CallStaticBooleanMethod)(JNIEnv*, jclass, jmethodID, ...); jbyte (*CallStaticByteMethod)(JNIEnv*, jclass, jmethodID, ...); jchar (*CallStaticCharMethod)(JNIEnv*, jclass, jmethodID, ...); jshort (*CallStaticShortMethod)(JNIEnv*, jclass, jmethodID, ...); jint (*CallStaticIntMethod)(JNIEnv*, jclass, jmethodID, ...); jlong (*CallStaticLongMethod)(JNIEnv*, jclass, jmethodID, ...); jfloat (*CallStaticFloatMethod)(JNIEnv*, jclass, jmethodID, ...); jdouble (*CallStaticDoubleMethod)(JNIEnv*, jclass, jmethodID, ...); void (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...); jobject (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...); jboolean (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...); jbyte (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...); jchar (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...); jshort (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...); jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...); jlong (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...); jfloat (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...); jdouble (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...); void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
JNI also offers Call…(…) functions that take args in a form ofva_list as well as an array ofjvalue-s: <Type> (*Call<Type>MethodV)(JNIEnv*, jobject, jmethodID, va_list); <Type> (*CallStatic<Type>MethodV)(JNIEnv*, jobject, jmethodID, va_list); <Type> (*Call<Type>MethodA)(JNIEnv*, jobject, jmethodID, jvalue*); <Type> (*CallStatic<Type>MethodA)(JNIEnv*, jobject, jmethodID, jvalue*); |
For example:
public class Foo { private String bar; public void setBar(String bar) { this.bar = bar; } … public native void processBar(); }
JNIEXPORT void JNICALL Java_com_marakana_jniexamples_Foo_processBar(JNIEnv *env, jobject object) { /* Same as object.getClass() */ jclass clazz = (*env)->GetObjectClass(env, object); if (clazz != NULL) { /* Same as clazz.getMethod("setBar", String.class) - assuming non-static */ jmethodID method = (*env)->GetMethodID(env, clazz, "setBar", "(Ljava/lang/String;)V"); if (method != NULL) { /* make sure we found the method */ /* Create a new Java String */ jstring jString = (*env)->NewStringUTF(env, "Bar2"); if (jString != null) { /* Same as method.invoke(object, jString) */ (*env)->CallVoidMethod(env, object, method, jString); } } } }
JNI supports JNI_OnLoad function, which if exported by the library, will automatically be called when the library is loaded bySystem.loadLibrary(String)
jint JNI_OnLoad(JavaVM *vm, void *reserved);
When used with JNI’s RegisterNatives, we can pre-bind native methods as soon as our library is first loaded, and consequently, we no longer need to usejavah:
#include <jni.h> static void sayHi(JNIEnv *env, jclass clazz, jstring who, jint times) { const char *name = (*env)->GetStringUTFChars(env, who, NULL); if (name != NULL) { jint i; for (i = 0; i < times; i++) { printf("Hello %s\n", name); } (*env)->ReleaseStringUTFChars(env, who, name); } } static JNINativeMethod method_table[] = { { "sayHi", "(Ljava/lang/String;I)V", (void *) sayHi } }; static int method_table_size = sizeof(method_table) / sizeof(method_table[0]); jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env; if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_6) != JNI_OK) { return JNI_ERR; } else { jclass clazz = (*env)->FindClass(env, "com/marakana/jniexamples/Hello"); if (clazz) { jint ret = (*env)->RegisterNatives(env, clazz, method_table, method_table_size); (*env)->DeleteLocalRef(env, clazz); return ret == 0 ? JNI_VERSION_1_6 : JNI_ERR; } else { return JNI_ERR; } } }
Notice that we JNI function that will be bound to the native Java method is declared as static. It won’t even be exported to the symbol table. It does not need to be becauseRegisterNatives will bind it via a function pointer to the Java method. |
JNI supports JNI_OnUnload, but since this is only invoked when the class-loader that loaded the library is GC’ed, it’s not commonly used
JNI permits us to throw exceptions from native code
Assuming we have a Throwable object:
jint Throw(JNIEnv *env, jthrowable obj);
Assuming we have a Throwable class, and that it has a constructor that takes a string message:
jint ThrowNew(JNIEnv *env, jclass clazz, const char *message);
For example:
static void ThrowExceptionByClassName(JNIEnv *env, const char *name, const char *message) { jclass clazz = (*env)->FindClass(env, name); if (clazz != NULL) { (*env)->ThrowNew(env, clazz, message); (*env)->DeleteLocalRef(env, clazz); } } … if (invalidArgument == TRUE) { ThrowExceptionByClassName(env, "java/lang/IllegalArgumentException", "This argument is not valid!"); }
Throwing an exception from native code just registers the throwable (as an exception pointer) with the current thread - itdoes not interrupt the current flow of the code!
The pending exception gets "thrown" upon returning to the Java code
While an exception is pending, we can only call the following JNI functions:
DeleteGlobalRef
DeleteLocalRef
DeleteWeakGlobalRef
ExceptionCheck
ExceptionClear
ExceptionDescribe
ExceptionOccurred
MonitorExit
PopLocalFrame
PushLocalFrame
Release<PrimitiveType>ArrayElements
ReleasePrimitiveArrayCritical
ReleaseStringChars
ReleaseStringCritical
ReleaseStringUTFChars
Calling methods on Java objects (e.g. via CallObjectMethod) can fail with an exception, but since exceptions don’t automatically abort our function calls, we must explicitly check for existence of pending exceptions (e.g. with ExceptionCheck)
Native code can "catch" an exception by calling ExceptionCheck(…) orExceptionOccurred(…), print its stack trace (to stderr) withExceptionDescribe(…), and "handle" it (i.e. clear it) withExceptionClear(…)
jthrowable ExceptionOccurred(JNIEnv *env); /* NULL if no exception is currently being thrown */ jboolean ExceptionCheck(JNIEnv *env); void ExceptionDescribe(JNIEnv *env); void ExceptionClear(JNIEnv *env);
To get to the message of a throwable object we would do something like:
… (*env)->CallObjectMethod(env, …); /* this can throw an exception */ if ((*env)->ExceptionCheck(env)) { jthrowable throwable = (*env)->ExceptionOccurred(env); (*env)->ExceptionDescribe(env); /* optionally dump the stack trace */ (*env)->ExceptionClear(env); /* mark the exception as "handled" */ jclazz clazz = (*env)->GetObjectClass(env, throwable); jmethodID getMessageMethod = (*env)->GetMethodID(env, clazz, "getMessage", "()Ljava/lang/String;"); jstring message = (*env)->CallObjectMethod(env, throwable, getMessageMethod); const char *cMessage = (*env)->GetStringUTFChars(env, message, NULL); if (cMessage) { printf("ERROR: %s\n", cMessage); (*env)->ReleaseStringUTFChars(env, message, cMessage); } (*env)->DeleteLocalRef(env, clazz); } …