Android ndk-jni语法—— 1

196 阅读6分钟

一.语法分析

接着上一节的NDK例子,能看到native-lib.cpp的代码:

#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_carey_myndk_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

首先是引入了jni头文件和string库。其中方法写法中有几个关键字:

JNIEXPORT: 是宏定义,表示允许该函数被外部调用。

JNICALL: 是用于约束函数入栈顺序和堆栈清理规则。

如果删除了上面两个关键字,代码也不会报错,系统编译时候会自动加上这俩关键字。

再看方法的名称Java_com_carey_myndk_MainActivity_stringFromJNI,这个是命名规范,是方法的唯一标识,以确保 Java 代码能够正确地调用到对应的本地(native)方法。规则如下:

Java_<包名(用点替换为下划线)><类名(用下划线连接单词)><方法名>

JNIEnv: 是一个结构体指针

struct _JNIEnv {
    /* do not rename this; it does not seem to be entirely opaque */
    const struct JNINativeInterface* functions;

#if defined(__cplusplus)

    jint GetVersion()
    { return functions->GetVersion(this); }

    jclass DefineClass(const char *name, jobject loader, const jbyte* buf,
        jsize bufLen)
    { return functions->DefineClass(this, name, loader, buf, bufLen); }

    jclass FindClass(const char* name)
    { return functions->FindClass(this, name); }

    jmethodID FromReflectedMethod(jobject method)
    { return functions->FromReflectedMethod(this, method); }

    jfieldID FromReflectedField(jobject field)
    { return functions->FromReflectedField(this, field); }

    // 省略...
    
    
    jstring NewStringUTF(const char* bytes)
    { return functions->NewStringUTF(this, bytes); }

    jobject NewObjectA(jclass clazz, jmethodID methodID, const jvalue* args)
    { return functions->NewObjectA(this, clazz, methodID, args); }

    jclass GetObjectClass(jobject obj)
    { return functions->GetObjectClass(this, obj); }

    jboolean IsInstanceOf(jobject obj, jclass clazz)
    { return functions->IsInstanceOf(this, obj, clazz); }

    jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
    { return functions->GetMethodID(this, clazz, name, sig); }

在这里调用了它的NewStringUTF()方法。这样通过JNIEnv可以访问Java中的方法、属性、创建对象、加载类等等。

jobject: 有两个含义:1.如果你的方法是native方法不是静态方法,那么jobject代表该方法对应的Java对象;2.如果你的native方法是静态方法,那么jobject代表该方法对应的class对象。

jstring: 它是jni中基本的数据类型,通过jni.h头文件中能看到下面代码:

/* Primitive types that match up with Java equivalents. */
typedef uint8_t  jboolean; /* unsigned 8 bits */
typedef int8_t   jbyte;    /* signed 8 bits */
typedef uint16_t jchar;    /* unsigned 16 bits */
typedef int16_t  jshort;   /* signed 16 bits */
typedef int32_t  jint;     /* signed 32 bits */
typedef int64_t  jlong;    /* signed 64 bits */
typedef float    jfloat;   /* 32-bit IEEE 754 */
typedef double   jdouble;  /* 64-bit IEEE 754 */

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;

为了方便理解,把java、jni和c/c++中类型的对应关系总结下,见下表:

image.png

extern "C":是一种特殊的语法,用于告诉C++编译器以C语言的方式来处理被extern "C"包裹的代码或声明。

二.实现在C中访问java属性成员

在MainActivity中定义属性成员name, 并声明native方法:

public String name = ",你好!"; // 昵称

public native String updateName(); // 更新昵称

创建新的native方法后,会爆红,鼠标放在方法名上点击提示会在cpp文件中自动创建该native方法的实现:

image.png

extern "C"
JNIEXPORT jstring JNICALL
Java_com_carey_myndk_MainActivity_updateName(JNIEnv *env, jobject thiz) {
    // TODO: implement updateName()
}

我们在这个方法中去实现调用MainActivity中的name属性,代码如下,已增加注释:

extern "C"
JNIEXPORT jstring JNICALL
Java_com_carey_myndk_MainActivity_updateName(JNIEnv *env, jobject jobj) {
    // 获取class对象
    jclass cls = (*env).GetObjectClass(jobj);
    // 获取对象中的属性,参数1 class对象,参数2 属性名,参数3 属性签名
    jfieldID fid = (*env).GetFieldID(cls, "name", "Ljava/lang/String;");
    // 获取属性值,参数1 class对象 参数2 属性
    jstring jStr = (jstring)(*env).GetObjectField(jobj, fid);
    // 将jstring类型转成c/c++ 字符串类型
    const char* str = (*env).GetStringUTFChars(jStr, NULL);
    // 分配足够的空间来存储新字符串,包括 "Carey" 和 str 以及终止符 '\0'
    size_t newStrLen = strlen("Carey") + strlen(str) + 1;
    // 分配内存空间
    char *newStr = (char *)malloc(newStrLen);
    if (newStr == NULL) {
        // 内存分配失败,处理错误
        (*env).ReleaseStringUTFChars(jStr, str);
        return (*env).NewStringUTF("");
    }
    // 拼接字符串
    strcpy(newStr, "Carey");
    strcat(newStr, str);
    // 创建新的Java字符串
    jstring newJStr = (*env).NewStringUTF(newStr);
    // 更新Java对象中的属性值
    (*env).SetObjectField(jobj, fid, newJStr);
    // 释放资源
    free(newStr);
    (*env).ReleaseStringUTFChars(jStr, str);
    // 返回新值
    return newJStr;
}

最后在MainActivity中调用updateName方法去显示更改后的昵称:

String newName = updateName(); // 更新name
tv.setText(newName);

效果如图:

image.png

三.jni中属性和对应的属性签名

在JNI(Java Native Interface)中,属性签名用于标识Java中的字段类型,以便在C/C++代码中正确访问这些字段。JNI中的属性签名与Java中的数据类型有着严格的对应关系。以下是对JNI中属性签名对应关系的详细解释:

1.基本类型签名

image.png

2.引用类型签名 JNI中的引用类型签名用于表示Java中的类、接口、数组等复杂类型。

1.类与接口

  • 引用类型的签名以“L”开头,以“;”结尾。
  • 在“L”和“;”之间,使用“/”代替Java中的点(.)来表示包名和类名。
  • 例如,java.lang.String的签名为Ljava/lang/String;

2.数组

  • 数组类型的签名以“[”开头,后跟数组元素的签名。
  • 对于多维数组,每增加一维就增加一个“[”。
  • 例如,int[]的签名为[IString[]的签名为[Ljava/lang/String;int[][]的签名为[[I

例子: 比如下面代码,有几个类成员:

public class Person {
    private String name;
    private int age;
    private boolean isAlive;
    private String[] hobbies;
}

在JNI中,这些字段的签名将分别为:

  • nameLjava/lang/String;
  • ageI
  • isAliveZ
  • hobbies[Ljava/lang/String;

在JNI代码中,当需要访问Java对象的字段时,需要使用GetFieldID函数来获取字段的ID。此时,需要提供Java类的jclass对象、字段的名称以及字段的签名。例如:

jclass cls = (*env)->GetObjectClass(jobject);
jfieldID fid_name = (*env)->GetFieldID(cls, "name", "Ljava/lang/String;");
jfieldID fid_age = (*env)->GetFieldID(cls, "age", "I");
jfieldID fid_isAlive = (*env)->GetFieldID(cls, "isAlive", "Z");
jfieldID fid_hobbies = (*env)->GetFieldID(cls, "hobbies", "[Ljava/lang/String;");

通过获取到的字段ID,就可以使用GetObjectFieldGetIntFieldGetBooleanField等函数来访问Java对象的字段了。

四.总结

学习jni ndk开发还是需要一定的c/c++基础,大家可以补充下这块的基础知识。下一篇我们会继续学习jni语法。喜欢的可以点赞+收藏。感谢!