JNI编程参考备忘----成员变量和方法

本文详细介绍了JNI中如何访问Java类的成员变量和方法,包括静态与实例变量,构造方法,以及局部、全局和弱全局引用的管理。此外,还涉及异常处理、Java虚拟机接口、线程管理和国际化支持等关键知识点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 对象成员变量的访问

  函数原型:

  jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);

 

代码实例:

JNIEXPORT void JNICALL Java_com_fyj_test_InstanceFieldAccess_accessField
  (JNIEnv *env, jobject obj)
{
    jfieldID fid;   /* store the field ID */
    jstring jstr;
   const char *str;

    /* get a reference to obj's class */
    jclass cls = (*env)->GetObjectClass(env, obj);
    printf("In C:\n");
    
    /* look for the instance field s in cls */
    fid = (*env)->GetFieldID(env, cls, "s", "Ljava/lang/String;");
    if (fid == NULL)
        return;

   /* Read the instance fields s */
    jstr = (*env)->GetObjectField(env, obj, fid);
    str = (*env)->GetStringUTFChars(env, jstr, NULL);
    if (str == NULL)
         return;
    printf("     s =  \"%s\"\n", str);
    (*env)->ReleaseStringUTFChars(env, jstr, str);
    
    /* create a new string and overwrite the instance field */
    jstr = (*env)->NewStringUTF(env, "123");
    if (jstr == NULL)
       return;
   (*env)->SetObjectField(env, obj, fid, jstr);
}

1. 类成员变量的访问

函数原型

jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);

 

代码示例:

JNIEXPORT void JNICALL Java_com_fyj_test_StaticFieldAccess_accessField
  (JNIEnv *env, jobject obj)
{
    jfieldID fid;
    jint si;

   jclass cls = env->GetObjectClass(obj);
   cout<<"In C++:\n";

   fid = env->GetStaticFieldID(cls, "si", "I");
   if (fid == NULL)
       return;

  si = env->GetStaticIntField(cls, fid);
  cout<<"   StaticFieldAccess.si = "<<si<<endl;
  env->SetStaticIntField(cls, fid, 200);
   
}

通过上述例子可知,在JNI层访问Java类的成员变量分为两步:

1. 通过调用GetFieldIDGetFieldID获得成员变量相应的ID实例。

2. 然后根据ID来获得对成员变量的引用。

3. 方法调用

   也包含实例方法的调用和类的方法调用。

步骤也是分为两步:

1. 通过调用GetMethodIDGetStaticMethodID获得方法ID

2. 通过ID调用CallVoidMethodCallStaticVoidMethod调用Java类的方法。

 

原型声明:

jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);

<NativeType> CallStatic<Type>MethodV(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);

 

代码示例:

JNIEXPORT void JNICALL Java_com_fyj_test_InstanceMethodCall_nativeMethod
  (JNIEnv *env, jobject obj)
{
    jclass cls = env->GetObjectClass(obj);
    jmethodID mid = env->GetMethodID(cls, "callback", "()V");
    if (mid == NULL)
        return;

   cout<<"In C++ "<<endl;
   env->CallVoidMethod(obj, mid);

4.调用构造方法

要获得构造方法的调用,需要传入“<init>作为方法名,”V”作为方法返回类型描述,然后通过调用NewObject函数调用。

jobject NewObjectA(JNIEnv *env, jclass clazz, jmethodID methodID, jvalue *args);

 

代码示例如下:

jstring
MyNewString(JNIEnv *env, jchar *chars, jint len)
{
   jclass stringClass;
    jmethodID cid;
    jcharArray elemArr;
    jstring result;

    stringClass = env->FindClass("java/lang/String");
    if (stringClass == NULL)
        return NULL;

    cid =  env->GetMethodID(stringClass, "<init>", "([C)V");
    if (cid == NULL)
        return NULL;

    elemArr = env->NewCharArray(len);
    if (elemArr == NULL)
        return NULL;

    env->SetCharArrayRegion(elemArr, 0, len, chars);
    result = (jstring)env->NewObject(stringClass, cid, elemArr);

   //free local references
    env->DeleteLocalRef(elemArr);
    env->DeleteLocalRef(stringClass);

    return result;
}

局部引用和全局引用

 

JNI支持三种类型的引用:局部引用,全局引用以及弱全局引用。

局部引用是自动被释放的,而全局引用和弱全局引用则是需要程序员显式释放的。

局部或全局引用阻止被引用的对象回收,而弱全局引用,则允许被引用的对象回收。

局部引用

大部分JNI函数产生局部引用。通过调用DeleteLocalRef释放局部引用对象。

 

全局引用

全局引用由唯一的一个JNI函数创建:NewGlobalRef

创建

jobject NewGlobalRef(JNIEnv *env, jobject obj);

释放

void DeleteGlobalRef(JNIEnv *env, jobject gref);

 

弱全局引用

创建弱全局引用函数:NewWeakGlobalRef

jweak NewWeakGlobalRef(JNIEnv *env, jobject obj);

释放弱全局引用函数:DeleteGlobalWeakRef

void DeleteWeakGlobalRef(JNIEnv *env, jobject wref);

 

引用比较

使用IsSameObject来比较两个引用对象是否指向同一个对象。

 

管理局部引用

JNI文档上规定虚拟机会自动确保每个本地方法可以创建至少16个局部引用。如果16个不够,则可以通过调用EnsureLocalCapacity来扩大允许创建的局部引用变量的个数。

另外,Push/PopLocalFrame函数允许程序员在一个嵌套的区域创建局部引用。

jint PushLocalFrame(JNIEnv *env, jint capacity);

jobject PopLocalFrame(JNIEnv *env, jobject result);

jint EnsureLocalCapacity(JNIEnv *env, jint capacity);


JNI 异常处理

 

这里的异常主要是指调用JNI函数产生的异常。先看一个代码实例:

JNIEXPORT void JNICALL Java_com_fyj_test_CatchThrow_doit
  (JNIEnv *env, jobject obj)
{
    jthrowable exc;
    jclass cls = env->GetObjectClass(obj);
    jmethodID mid =
    env->GetMethodID(cls, "callback", "()V");
    if (mid == NULL) {
        return;
    }
    env->CallVoidMethod(obj, mid);
    exc = env->ExceptionOccurred();
    if (exc) {
    /* We don't do much with the exception, except that
        we print a debug message for it, clear it, and
        throw a new exception. */
        jclass newExcCls;
        env->ExceptionDescribe();
        env->ExceptionClear();
        newExcCls = env->FindClass(
                            "java/lang/IllegalArgumentException");
        if (newExcCls == NULL) {
            /* Unable to find the exception class, give up. */
            return;
        }
        env->ThrowNew(newExcCls, "thrown from C code");
    }
}

如上代码所示:

判断JNI函数调用是否产生异常用如下函数:

jthrowable ExceptionOccurred(JNIEnv *env);

returns a local reference to the pending exception, or returns NULL if there is no pending excep-

tion.

jboolean ExceptionCheck(JNIEnv *env);

返回JINI_TRUE表示发生了异常,否则无异常。

打印异常信息用如下函数:

void ExceptionDescribe(JNIEnv *env);

 

清除异常使用如下函数:

void ExceptionClear(JNIEnv *env);

 

抛出异常使用如下函数:

jint ThrowNew(JNIEnv *env, jclass clazz, const char *message);


Java虚拟机调用接口

有两个重要的数据结果对象:JavaVMJEnv,其中JavaVM是在所有线程中都有效,而JEnv则只在当前线程中有效。

创建虚拟机

函数原型

jint JNI_CreateJavaVM(JavaVM **pvm, void **penv, void *vm_args);

对于Java 2 SDK

JavaVMInitArgs vm_args;
JavaVMOption options[4];
/* disable JIT */
options[0].optionString =
"-Djava.compiler=NONE";
/* user classes */
options[1].optionString =
"-Djava.class.path=c:\\myclasses";
/* native lib path */
options[2].optionString =
"-Djava.library.path=c:\\mylibs";
/* print JNI msgs */
options[3].optionString = "-verbose:jni";
vm_args.version = JNI_VERSION_1_2;
vm_args.options = options;
vm_args.nOptions = 4;
vm_args.ignoreUnrecognized = TRUE;
res = JNI_CreateJavaVM(&vm, (void **)&env,
&vm_args);
if (res < 0) {
... /* error occurred
}

其中相关的数据结果定义如下:

typedef struct JavaVMInitArgs {
    jint version;
    jint nOptions;
    JavaVMOption *options;
    jboolean ignoreUnrecognized;
} JavaVMInitArgs;

typedef struct JavaVMOption {
    char *optionString;
    void *extraInfo;
} JavaVMOption;

关联本地线程

当在Native代码中访问JNI的接口(此线程中尚未创建JEnv对象),必须调用如下的接口:

 

jint AttachCurrentThread(JavaVM *vm, void **penv, void *args);

 

通过上述调用,前当前线程关联进当前虚拟机中,取得了当前线程的JEnv对象的指针,这样,在当前线程中就可以调用JNI接口了。通过如下函数取消关联:

jint DetachCurrentThread(JavaVM *vm);

 

在任何时候,可以通过如下方式获得当前线程的JEnv对象:

JavaVM *jvm; /* already set */
f()
{
    JNIEnv *env;
    (*jvm)->AttachCurrentThread(jvm, (void **)&env, NULL);
    ... /* use env */
}

JNI线程

JEnv对象的引用只在当前线程有效,不能从一个线程传递到另一个线程。本地引用对象只能在当前线程中访问,不能传递到另一个线程中。

JNI层,可以通过如下接口实现同步访问一段代码:


if ((*env)->MonitorEnter(env, obj) != JNI_OK) {
... /* error handling */
}
...
/* synchronized block */
if ((*env)->MonitorExit(env, obj) != JNI_OK) {
... /* error handling */
};

国际化支持

Java虚拟机用Unicode的格式表示字符串,而大多数本地平台使用本地语言相关的字符串编码格式表示字符串,所以,在两者之间进行转换时,不能使用GetStringUTFChars和GetStringUTFRegion。而应当使用String(byte[] bytes)将本地字符串转换为jstring, 使用String.getBytes方法将jstring转换为本地字符串。

注册本地方法

通常在Java类中调用执行本地方法,需要经历如下两步:

1. 通过System.loadLibrary定位和加载指定的库。

2. Java虚拟机在加载的本地库中定位对应的本地方法,如Foo.g本地方法调用将会使虚拟机查看和链接位于库的本地函数:Java_Foo_g(注:本地方法的命名必须符合JNI函数命令规范)

对于第二步,可以使用另一种方式,即通过如下方式:

 

static JNINativeMethod nms[] = {
   {“g”, “()v”, (void*)&g_impl},
};
(*env)->RegisterNatives(env, cls, nms, sizeof(nms)/sizeof(nms[0]));

通过上述方式,不再依赖虚拟机在已经加载的库中搜索要调用的本地方法。

装载和卸载处理器

当调用System.loadLibary加载本地库时,虚拟机会调用装载处理器函数:

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved);

通过这个接口,可以获得指向当前JavaVM虚拟机实例,既而获得当前线程的JavaEnv实例:

if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_2)) {

return JNI_ERR; /* JNI version not supported */

}

当虚拟机卸载本地库时,也会调用相应的卸载处理器函数:

JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *jvm, void *reserved);

 

Peer

Java类的设计中,通常会定义一个32bitint64bitlong私有成员变量来代表一个对应的Peer类,它主要是给Native层访问的,在Java层,它就是一个普通的intlong型的数据,而在JNI层,它则是指向一个本地数据结构的地址。这个本地数据结构要么本身就是或者内部包含一个指向Java类相对应的本地类(Peer类)的变量。这样,通过Java本地接口的调用,会将调用传递给Peer类的对应接口。

这种设计方法通常出现在为C++类定义相对应的Java类的时候,C++类就是Java类对应的Peer类。如android中的Parcel类。

反之,亦然。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值