JNI 总结

184 阅读9分钟

Java Native Interface (JNI) 以下情况下使用jni:

  • 标准 Java 类库不支持应用程序所需的平台相关特性。

  • 您已经有一个用另一种语言编写的库,并希望通过 JNI 使其可供 Java 代码访问。

  • 您想用较低级别的语言(例如汇编)实现一小部分时间要求严格的代码。 使用jni可以实现:

  • 创建、检查和更新 Java 对象(包括数组和字符串)。

  • 调用 Java 方法。

  • 捕获并抛出异常。

  • 加载类并获取类信息。

  • 执行运行时类型检查。

JNI 接口指针只在当前线程中有效。因此,本机方法不得将接口指针从一个线程传递到另一个线程。

本机方法接收 JNI 接口指针作为参数。当 VM 从同一个 Java 线程多次调用本机方法时,它保证将相同的接口指针传递给本机方法。但是,本地方法可以从不同的 Java 线程调用,因此可能会接收不同的 JNI 接口指针。

加载本机方法

package pkg;  

class Cls { 

     native double f(int i, String s); 

     static { 

         System.loadLibrary(“pkg_Cls”); 

     } 

} 

System.loadLibrary的参数是程序员任意选择的库名称。系统遵循标准但特定于平台的方法将库名称转换为本机库名称。例如,Solaris 系统将名称转换pkg_Clslibpkg_Cls.so,而 Win32 系统将相同的pkg_Cls名称转换为pkg_Cls.dll.

程序员还可以调用 JNI 函数RegisterNatives()来注册与类关联的本地方法。该RegisterNatives()函数对静态链接函数特别有用。

解析本机方法名称

本机方法名称由以下组件连接:

  • 前缀Java_

  • 完全限定的类名

  • 下划线分隔符 ("_ ")

  • 方法名称

  • 对于重载的本机方法,两个下划线"__"后跟参数签名

使用下划线"_"字符代替完全限定类名中的斜杠(“/”) 由于名称或类型描述符从不以数字开头,因此我们可以使用_0, ..._9作为转义序列,如表所示:

转义序列表示
_0XXXX一个 Unicode 字符XXXX。 请注意,小写用于 表示非 ASCII Unicode 字符,例如, _0abcd_0ABCD.
_1_
_2; 
_3[

本机方法参数

代码示例说明了使用 C 函数来实现本机方法f。本机方法f声明如下:

package pkg;  

class Cls { 

     native double f(int i, String s); 

     ... 

} 

具有长名称的 C 函数Java_pkg_Cls_f_ILjava_lang_String_2实现了本机方法f

代码示例使用 C 实现本机方法

jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (
     JNIEnv *env, /* 接口指针 */
     jobject obj, /* "this" 指针 */
     jin i, / * 参数 # 1 * /
     jstring s) /* 参数 #2 */
{
     /* 获取 Java 字符串的 C 副本 */
     const char *str = (*env)->GetStringUTFChars(env, s, 0);

     /* 处理字符串 */
     ...

     /* 现在我们完成了 str */
     (*env)->ReleaseStringUTFChars(env, s, str);

     return ...
}

代码示例使用 C++ 实现本机方法:

extern "C" /* 指定 C 调用约定 */ 

jdouble Java_pkg_Cls_f__ILjava_lang_String_2 ( 

     JNIEnv *env, /* 接口指针 */ 

     jobject obj, /* "this" 指针 */ 

     jin i, / * 参数 # 1 * / 

     jstring s) /* 参数 #2 */ 

{ 

     const char *str = env->GetStringUTFChars(s, 0); 

     ... 

     env->ReleaseStringUTFChars(s, str); 

     return ... 

} 

原始类型

Java TypeNative TypeDescription
booleanjbooleanunsigned 8 bits
bytejbytesigned 8 bits
charjcharunsigned 16 bits
shortjshortsigned 16 bits
intjintsigned 32 bits
longjlongsigned 64 bits
floatjfloat32 bits
doublejdouble64 bits
voidvoidN/A

为方便起见,提供以下定义。

#define JNI_FALSE 0  
#define JNI_TRUE 1 

jsize整数类型用于描述基数索引和大小:

typedef jit jsize; 

引用类型

在 C 中 ,所有其他 JNI 引用类型都被定义为与 jobject 相同。例如:

typedef jobject jclass; 

在 C++ 中,JNI 引入了一组虚拟类来强制执行子类型关系。例如:

class _jobject {}; 
class _jclass : public _jobject {}; 
... 
typedef _jobject *jobject; 
typedef _jclass *jclass; 

字段(Field)和方法 ID

方法和字段 ID 是常规的 C 指针类型:

struct _jfieldID;              /* 不透明结构 */ 
typedef struct _jfieldID *jfieldID;   /* 字段 ID */ 
 
struct _jmethodID;              /* 不透明结构 */ 
typedef struct _jmethodID *jmethodID; /* 方法 ID */ 

值类型

用作参数数组中的元素类型。声明如下:

typedef union jvalue { 
    jboolean z; 
    jbyte    b; 
    jchar    c; 
    jshort   s; 
    jint     i; 
    jlong    j; 
    jfloat   f; 
    jdouble  d; 
    jobject  l; 
} jvalue; 

类型签名

JNI 使用 Java VM 的类型签名表示。表显示了这些类型签名。

表Java VM 类型签名

类型签名Java 类型
Zboolean
Bbyte
Cchar
Sshort
Iint
Jlong
Ffloat
Ddouble
L fully-qualified-class ;fully-qualified-class
[typetype[]
(arg-types) ret-type (参数)返回值method type

例如,Java 方法:

long f (int n, String s, int[] arr); 

具有以下类型签名:

(ILjava/lang/String;[I)J 

接口功能表

标题
获取版本jint GetVersion(JNIEnv *env);
定义类jclass DefineClass(JNIEnv *env, const char *name, jobject loader, const jbyte *buf, jsize bufLen);
查找类jclass FindClass(JNIEnv *env, const char *name);
获取超类jclass GetSuperclass(JNIEnv *env, jclass clazz);
是否可转换jboolean IsAssignableFrom(JNIEnv *env, jclass clazz1, jclass clazz2);
Throwjint Throw(JNIEnv *env, jthrowable obj);
ThrowNewjint ThrowNew(JNIEnv *env, jclass clazz, const char *message);
发生异常jthrowable ExceptionOccurred(JNIEnv *env);
异常描述void ExceptionDescribe(JNIEnv *env);
异常清除void ExceptionClear(JNIEnv *env);
致命错误void FatalError(JNIEnv *env, const char *msg);
异常检查jboolean ExceptionCheck(JNIEnv *env);
创建全局引用jobject NewGlobalRef(JNIEnv *env, jobject obj);
删除全局引用void DeleteGlobalRef(JNIEnv *env, jobject globalRef);
删除本地引用void DeleteLocalRef(JNIEnv *env, jobject localRef);
确保本地容量jint EnsureLocalCapacity(JNIEnv *env, jint capacity);
PushLocalFramejint PushLocalFrame(JNIEnv *env, jint capacity);
PopLocalFramejobject PopLocalFrame(JNIEnv *env, jobject result);
创建本地引用jobject NewLocalRef(JNIEnv *env, jobject ref);
创建弱全局引用jweak NewWeakGlobalRef(JNIEnv *env, jobject obj);
删除弱全局引用void DeleteWeakGlobalRef(JNIEnv *env, jweak obj);
分配对象jobject AllocObject(JNIEnv *env, jclass clazz);
创建对象jobject NewObject(JNIEnv *env, jclass clazz, jmethodID methodID, ...);
创建对象jobject NewObjectA(JNIEnv *env, jclass clazz,jmethodID methodID, const jvalue *args);
创建对象jobject NewObjectV(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);
获取对象类jclass GetObjectClass(JNIEnv *env, jobject obj);
获取对象引用类型jobjectRefType GetObjectRefType(JNIEnv* env, jobject obj);
是否是类的实例jboolean IsInstanceOf(JNIEnv *env, jobject obj, jclass clazz);
是否同一个对象jboolean IsSameObject(JNIEnv *env, jobject ref1, jobject ref2);
获取字段IDjfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
获取字段ID的值NativeType Get<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID);
设置字段ID的值void Set<type>Field (JNIEnv *env, jobject obj, jfieldID fieldID, NativeType value);
获取方法IDjmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
调用java方法NativeType Call<type>Method(JNIEnv *env, jobject obj, jmethodID methodID, ...);
调用java方法NativeType Call<type>MethodA (JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args);
调用java方法NativeType Call<type>MethodV (JNIEnv *env, jobject obj, jmethodID methodID, va_list args);
调用java方法的结果NativeType CallNonvirtual<type>Method(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...);
调用java方法的结果NativeType CallNonvirtual<type>MethodA (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, const jvalue *args);
调用java方法的结果NativeType CallNonvirtual<type>MethodV(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, va_list args);
获取静态字段 IDjfieldID GetStaticFieldID(JNIEnv *env, jclass clazz,const char *name, const char *sig);
获取静态字段 ID的值NativeType GetStatic<type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID);
设置静态字段的值void SetStatic<type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID, NativeType value);
获取静态方法 IDjmethodID GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
获取静态方法 ID的值NativeType CallStatic<type>Method(JNIEnv *env, jclass clazz, jmethodID methodID, ...);
获取静态方法 ID的值NativeType CallStatic<type>MethodA(JNIEnv *env, jclass clazz, jmethodID methodID, jvalue *args);
获取静态方法 ID的值NativeType CallStatic<type>MethodV(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);
创建字符串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);
创建字符串UTFjstring NewStringUTF(JNIEnv *env, const char *bytes);
获取字符串UTF长度jsize GetStringUTFLength(JNIEnv *env, jstring string);
获取字符串UTF字符const char * GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy);
释放字符串UTF字符void ReleaseStringUTFChars(JNIEnv *env, jstring string, const char *utf);
获取字符串区域void GetStringRegion(JNIEnv *env, jstring str, jsize start, jsize len, jchar *buf);
获取字符串UTF区域void GetStringUTFRegion(JNIEnv *env, jstring str, jsize start, jsize len, char *buf);
获取字符串字符const jchar * GetStringCritical(JNIEnv *env, jstring string, jboolean *isCopy);
释放字符串字符void ReleaseStringCritical(JNIEnv *env, jstring string, const jchar *carray);
获取数组长度jsize GetArrayLength(JNIEnv *env, jarray array);
创建对象数组jobjectArray NewObjectArray(JNIEnv *env, jsize length, jclass elementClass, jobject initialElement);
获取对象数组元素jobject GetObjectArrayElement(JNIEnv *env,jobjectArray array, jsize index);
设置对象数组元素void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject value);
创建原始java数组ArrayType New<PrimitiveType>Array (JNIEnv *env, jsize length);
获得原始数组的函数NativeType *Get<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, jboolean *isCopy);
释放原始数组的函数void Release<PrimitiveType>ArrayElements (JNIEnv *env, ArrayType array, NativeType*  elems, jint mode);
获取原始数组的区域void Get<PrimitiveType>ArrayRegion(JNIEnv *env,ArrayType array,jsize start, jsize len, NativeType *buf);
设置原始数组的区域void Set<PrimitiveType>ArrayRegion (JNIEnv *env, ArrayType  const  NativeType array, jsize start, jsize len, *buf);
获取原始数组的区域void * GetPrimitiveArrayCritical(JNIEnv *env, jarray array, jboolean *isCopy);
释放原始数组的区域void ReleasePrimitiveArrayCritical(JNIEnv *env, jarray array, void *carray, jint mode);
注册本地方法jint RegisterNatives(JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint nMethods);
注销本地方法jint UnregisterNatives(JNIEnv *env, jclass clazz);
监视器输入jint MonitorEnter(JNIEnv *env, jobject obj);
监视器退出jint MonitorExit(JNIEnv *env, jobject obj);
创建ByteBufferjobject NewDirectByteBuffer(JNIEnv* env, void* 地址, jlong capacity);
获取直接缓冲区地址void* GetDirectBufferAddress(JNIEnv* env, jobject buf);
获取直接缓冲容量jlong GetDirectBufferCapacity(JNIEnv* env, jobject buf);
将对象转换为方法IDjmethodID FromReflectedMethod(JNIEnv *env, jobject method);
将字段转换为字段IDjfieldID FromReflectedField(JNIEnv *env, jobject field);
将方法ID转换为对象jobject ToReflectedMethod(JNIEnv *env, jclass cls, jmethodID methodID, jboolean isStatic);
将字段ID转换为对象jobject ToReflectedField(JNIEnv *env, jclass cls, jfieldID fieldID, jboolean isStatic);
获取JavaVMjint GetJavaVM(JNIEnv *env, JavaVM **vm);

API调用

标题
加载本机库jint JNI_OnLoad(JavaVM *vm, void *reserved);
卸载本机库void JNI_OnUnload(JavaVM *vm, void *reserved);
获得虚拟机的默认配置jint JNI_GetDefaultJavaVMInitArgs(void *vm_args);
获得所有已创建的虚拟机jint JNI_GetCreatedJavaVMs(JavaVM **vmBuf, jsize bufLen, jsize *nVMs);
加载并初始化虚拟机jint JNI_CreateJavaVM(JavaVM **p_vm, void **p_env, void *vm_args);
销毁虚拟机jint DestroyJavaVM(JavaVM *vm);
附加当前线程jint AttachCurrentThread(JavaVM *vm, void **p_env, void *thr_args);
附加当前线程作为守护进程jint AttachCurrentThreadAsDaemon(JavaVM* vm, void** penv, void* args);
分离当前线程jint DetachCurrentThread(JavaVM *vm);

如何在jni中C/C++层打印log到logcat

    1. CmakeLists中配置
find_library(
        log-lib
        log)
    
target_link_libraries(
        xxx 
        ${log-lib})
    1. 在要使用LOG的cpp文件中加入: #include <android/log.h>
    1. 直接使用__android_log_print函数, 代码举例:
char * name = "mronion";
__android_log_print(ANDROID_LOG_INFO, "lclclc", "my name is %s\n", name); //log i类型
    1. 改进

直接使用__android_log_print太麻烦了,我们可以定义一些log的方法,上述第二步改为:

#include <android/log.h>
#define TAG "projectname" // 这个是自定义的LOG的标识   
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型   
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型   
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) // 定义LOGW类型   
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) // 定义LOGE类型   
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__) // 定义LOGF类型 

使用举例:

char * name = "mronion";
LOGD("my name is %s\n", name );