JNI 转载&安利&补充

本文详细解析了Android开发中JNI与NDK的应用技巧,包括环境搭建、编译配置及跨语言调用等关键技术点。

推荐

标题大大的推荐,显示了我对将要安利的文章的推崇:Android:JNI 与 NDK到底是什么?(含实例教学)。这篇文章非常详细的记录了 AS 的 2.2 版本之前 JNI 的开发步骤。在这基础上我会做一些修修补补的工作。

修补一 : ndk 环境配置

在安利的博文中其实已经有了MAC相关的环境配置操作,可以看成 Linux 也是如此。我要补充的是 Win10 的环境配置,直接上图:

下载

这里写图片描述

环境配置
我的 ndk 路径:E:\android\sdk\ndk-bundle
第一步: NDK_ROOT

这里写图片描述

第二步:PATH 中填写 %NDK_ROOT%

这里写图片描述

修补二 : ndk-build 不是内部或外部命令,也不是可运行的程序

在配置完以上的环境变量之后,我在 AS 的 Terminal 窗口切换到 jni 目录之后,却依然出现了以上的错误:

这里写图片描述

究竟为什么我也懒得管,毕竟我只是偶尔使用 windows 而已,大部分时候,都是跑着 Linux 开发,给个解决方案: ndk-build -C Project_JNI_PATH

这里写图片描述

如上图所示,我先切换到 ndk 的下载路径,然后直接调用 ndk-build -C 命令,输入想要编译的 JNI 文件夹,然后成功编译。最后给一个 ndk-build 的命令博客链接,记不得了直接看一下:NDK开发之ndk-build命令详解

修补三 : 基于 AS 的2.2 版本 Your project contains C++ files but it is not using a supported native build system.

错误:这里写图片描述

贴出我的 build.gradle & gradle.properties

这里写图片描述

这里写图片描述


至此一些环境,编译的部分的修补到此为止。接下来,就是对于 JNI 部分技术的补充。

JNI 技术补充

补充一:JNI C/C++ 调用 Java

在大部分 JNI 的文章中大多只介绍了 Java 如何调用 Native 的过程,却没有介绍到 Native 是如何调用 Java 的,以下我写了一个简单的调用以供参考。
这里写图片描述
如上图,changeTextColorByJNI() 这个方法的功能就像方法名所描述的那样,通过 Native 层来调用 MainActivit 中的函数:
这里写图片描述
接下来,来看下 test.cpp 里面的代码吧:
这里写图片描述
就像开篇介绍的文章里面所说的那样 JNIEXPORT & JNICALL 这两个关键字是不能少的,你可以理解为这是一种标记。如上图所看到的其实到这里,也就结束了。最终,这个方法会调用到 MainActivity 中的 changeTextColor 方法,将字体变成红色。
那么接下来,就是详细的讲解了。
从 getFromJNI 和 changeTextColorByJNI 方法中其实都可以看出来,他们的方法参数里面都多了这样两个参数: JNIEnv 和 jobject。那么这两个参数到底代表了什么意思呢。

JNIEnv:

JNIEnv 是一个与线程相关的代表JNI环境的结构体。用一种不够全面的通俗的话来讲,JNIEnv 是就是为我们能处理 Java 而存在的,它可以调用 Java 函数,可以操作 jobject 对象的很多事情。对于我个人来说,我甚至是把它看成是 JVM 在JNI 环境中的代表似的存在,虽然这样绝对不全面。这样看的理由:JNIEnv 是 通过调用 JVM 的 AttachCurrentThread 函数所得到的。

jobject:

通过介绍的文章我们可以知道 除了基本数据类型、Class 和 string,其他 Java 层的对象在 JNI 中都是用 jobject 所代表的,那么这里的 jobject 其实代表的就是 MainActivity 这个对象。它的意思就是,我们实在 MainActivity 的哪一个对象上调用 changeTextColor 方法的。

调用Java 方法第一步: 通过 JNIEnv 获得 MainActivity 在 JNI 层所对应的 jclass 实例
jclass mainActivityInterface = env -> FindClass("study/jni/com/staticregister/MainActivity");
调用Java 方法第二步: 取出 MainActivity 类中函数 changeTextColor 的 jmethodID
jmethodID mChangeTextColorByJNI = env -> GetMethodID(mainActivityInterface, "changeTextColor", "()V");
调用Java 方法第三步: 调用 JNIEnv 的 CallVoidMethod 函数
env->CallVoidMethod(obj, mChangeTextColorByJNI);

至此 Native 调用 Java 的简单实例就完成了。其实在 JNIEnv 中还提供了大量的其他的方法例如:

获取字段的方法:
jfieldID GetFieldID(jclass clazz, const char *name, const char *sig)
NativeType Get<type>Field(JNIEvn *evn, jobject obj, jfieldID fieldID)
....

调用不同方法的:
NativeType Call<type>Method(JNIEvn *env, jobject obj, jmethodID methodID, params...)
&lt;think&gt; 我们正在讨论JNI(Java Native Interface)中的代码片段,特别是关于`&amp;v11`的含义。根据提供的引用[3]中的代码片段,我们看到如下内容: ```c std::allocator&lt;char&gt; __a; // [sp+8h] [bp-1Ch] std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; v6; // [sp+Ch] [bp-18h] std::allocator&lt;char&gt; v7; // [sp+14h] [bp-10h] int v8; // [sp+18h] [bp-Ch] v2 = env; std::string::string(&amp;v6, &quot;Hello from C++&quot;, &amp;__a); ``` 在这段代码中,`&amp;v6`被传递给了`std::string`的构造函数。这里的`&amp;v6`表示取变量`v6`的地址。 在C++中,`&amp;`运算符用于获取变量的地址。因此,`&amp;v11`(如果存在变量`v11`)同样表示取变量`v11`的地址。 ### 1. **`&amp;v11`的含义** - 在C/C++中,`&amp;`是取地址运算符,`&amp;v11`表示获取变量`v11`的内存地址。 -JNI的上下文中,变量名如`v11`通常是编译器生成的临时变量局部变量(特别是在反汇编混淆代码中常见)。这些变量可能是基本类型(如`int`、`jobject`等)对象类型(如`std::string`)。 - 例如,在引用[3]的代码中,`&amp;v6`用于初始化一个`std::string`对象,将字符串&quot;Hello from C++&quot;的内容复制到`v6`中。 ### 2. **使用场景** - **传递对象地址**:在函数调用中,如果需要修改一个对象,通常会传递该对象的地址(指针)。例如,构造函数`std::string::string`的第一个参数是`std::string*`(即指向字符串对象的指针),所以需要传递`&amp;v6`。 - **与JNI函数交互**:在JNI中,有时需要将局部变量的地址传递给JNI函数。例如,获取数组元素时可能会使用`env-&gt;GetIntArrayRegion(array, start, len, &amp;result)`,其中`&amp;result`表示将结果复制到`result`变量的地址上。 - **结构体/对象初始化**:如引用[3]中,`std::string`的构造函数需要传入一个指针(即新对象的内存地址)。 ### 3. **相关注意事项** - **变量作用域**:确保变量`v11`在作用域内有效。如果`v11`是局部变量,其地址仅在当前函数栈中有效,不能传递给外部长期保存(否则会成为悬空指针)。 - **空指针与野指针**:确保`v11`已被正确初始化,否则`&amp;v11`可能指向无效内存。 - **类型匹配**:接收地址的函数方法必须期望一个指针类型,且类型匹配。例如,如果函数需要一个`jint*`(指向`jint`的指针),则不能传递`&amp;v11`(当`v11`是其他类型时)。 - **JNI异常**:在JNI中操作对象指针时,如果JNI函数调用引发异常(如`NullPointerException`),需要检查并处理异常[^1]。 ### 4. **在JNI中的典型用法示例** 以下是一个简单的示例,展示如何在JNI函数中使用`&amp;`取地址操作: ```c JNIEXPORT jintArray JNICALL Java_com_example_MyClass_processIntArray( JNIEnv *env, jobject obj, jintArray array) { // 获取数组长度 jsize length = env-&gt;GetArrayLength(array); jintArray result = env-&gt;NewIntArray(length); if (result == NULL) { return NULL; // 内存不足 } // 分配临时缓冲区 jint* buffer = new jint[length]; if (buffer == NULL) { return NULL; } // 将Java数组复制到本地缓冲区 env-&gt;GetIntArrayRegion(array, 0, length, buffer); // 注意:这里buffer本身是地址,不需要&amp; // 处理数据(例如,每个元素加1) for (int i = 0; i &lt; length; i++) { buffer[i] += 1; } // 将结果设置回Java数组 env-&gt;SetIntArrayRegion(result, 0, length, buffer); // 同样,buffer是地址 delete[] buffer; return result; } ``` 在这个例子中,我们使用`buffer`(即数组的首地址)来操作数据,而不是直接使用`&amp;`运算符。但如果是单个变量,比如: ```c jint value; env-&gt;GetIntField(obj, fieldID, &amp;value); // 错误!GetIntField不这样用 ``` 注意:实际上,`GetIntField`返回的是基本类型的值,不需要传递地址。但如果是获取数组的区域,则使用`Get&lt;Type&gt;ArrayRegion`函数,需要传递一个指向缓冲区的指针(即地址)。 ### 5. **混淆代码中的`&amp;v11`(引用[3]分析)** 在引用[3]的混淆代码中: ```c std::string::string(&amp;v6, &quot;Hello from C++&quot;, &amp;__a); ``` - `&amp;v6`:传递`v6`的地址给`std::string`的构造函数,用于在`v6`的内存位置构造一个字符串对象。 - `&amp;__a`:传递分配器`__a`的地址。 这种用法是合法的,但混淆后的代码变量名(如`v6`)通常无实际意义,增加了理解难度。 ### 6. **总结** - **`&amp;v11`**:表示获取变量`v11`的内存地址,常用于传递指针给函数。 - **在JNI中**:常用于本地缓冲区操作、对象构造、JNI数组函数等场景。 - **注意事项**:确保地址有效性、类型匹配和异常处理。 ### 相关问题 1. JNI中如何安全地处理本地引用以避免内存泄漏? 2. 在JNI中,如何正确使用`Get&lt;Type&gt;ArrayRegion`和`Set&lt;Type&gt;ArrayRegion`函数? 3. 如何调试JNI中的内存地址错误(如空指针越界)? 4. 混淆后的JNI代码有哪些常见特征和反混淆技巧? [^1]:【NDK系列11】Java和c++交互 Jni初识。 [^2]:查看Android源码中JNINativeMethod结构体 Android源码查看 由三个指针组成: 第一个为函数名字、第二个为函数签名 、第三个为本地函数地址 typedef struct { const char* name; const char* signature; void* fnPtr; } JNINativeMethod;。 [^3]:JNINDK开发(5)_使用Obfuscator-LLVM混淆JNI代码。2、使用IDA查看不混淆so文件 jstring __fastcall Java_com_rzr_llvm_MainActivity_stringFromJNI(JNIEnv *env, jobject a2) { JNIEnv *v2; // r4 _jstring *v3; // r4 jstring result; // r0 std::allocator&lt;char&gt; __a; // [sp+8h] [bp-1Ch] std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; v6; // [sp+Ch] [bp-18h] std::allocator&lt;char&gt; v7; // [sp+14h] [bp-10h] int v8; // [sp+18h] [bp-Ch] v2 = env; std::string::string(&amp;v6, &quot;Hello from C++&quot;, &amp;__a); v3 = (_jstring *)((int (__fastcall *)(JNIEnv *, unsigned __int8 *))v2-&gt;functions-&gt;NewStringUTF)( v2, v6._M_dataplus._M_p); std::string::_Rep::_M_dispose( (std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt;::_Rep *const )v6._M_dataplus._M_p - 1, &amp;v7); result = (jstring)(_stack_chk_guard - v8); if ( _stack_chk_guard == v8 ) result = v3; return result; }。 [^4]:android /inputmanager-jni,Android InputManager分析。// mFirstPostImeInputStage = earlyPostImeStage; new EarlyPostImeInputStage(nativePostImeStage); 注意参数为nativePostImeStage,在 apply(q, onProcess(q)) 返回forward时会用到。 &lt;/think&gt; ### JNI中 `&amp;v11` 的含义与用法详解 #### 1. **`&amp;v11` 的基本含义** 在 JNI 的 C/C++ 代码中,`&amp;v11` 表示 **取变量 `v11` 的内存地址**: - `&amp;` 是 C/C++ 的取地址运算符 - `v11` 通常是局部变量临时变量 - 常见于以下场景: - 传递对象地址给函数(如构造函数) - 获取缓冲区指针 - 处理数据结构引用 &gt; 示例代码(参考引用[3]): &gt; ```c &gt; std::string::string(&amp;v6, &quot;Hello from C++&quot;, &amp;__a); &gt; // 这里 &amp;v6 表示将 v6 的地址传递给 string 构造函数 &gt; ``` #### 2. **在 JNI 中的典型使用场景** ##### (1) 对象初始化 当需要初始化对象时,传递对象地址: ```c MyClass v11; // 声明对象 initializeObject(&amp;v11); // 传递地址进行初始化 ``` ##### (2) JNI 数组操作 处理 Java 数组时需要地址指针: ```c jintArray javaArray = ...; jint* nativeArray = env-&gt;GetIntArrayElements(javaArray, NULL); modifyArray(&amp;nativeArray[0], length); // 传递首元素地址 ``` ##### (3) 结构体/类成员访问 访问复杂数据结构: ```c typedef struct { jfieldID field1; jmethodID method1; } NativeData; NativeData v11; env-&gt;GetFieldID(v11.field1, ...); // 通过地址访问成员 ``` #### 3. **反混淆中的特殊表现(参考引用[3])** 在混淆代码中,`v11` 这类变量名常见特征: - 使用短变量名(`v1`, `v2`, ..., `vN`) - 类型通常是基础类型标准库对象 - 常与栈指针操作相关(如 `[sp+xxh]`) &gt; 反混淆示例: &gt; ```c &gt; // 混淆代码 &gt; int v8; // [sp+18h] [bp-Ch] &gt; &gt; // 实际可能对应 &gt; int stackGuardCheckValue; &gt; ``` #### 4. **关键注意事项** 1. **作用域管理** `&amp;v11` 仅在当前函数栈有效,不可跨线程使用: ```c void unsafe_function() { jobject localObj = ...; store_global_pointer(&amp;localObj); // 错误!局部变量退出后失效 } ``` 2. **空指针风险** 使用前必须初始化: ```c jobject v11 = NULL; // 必须初始化 env-&gt;CallVoidMethod(&amp;v11, ...); // 否则崩溃 ``` 3. **类型严格匹配** 地址类型需与接收参数一致: ```c void process(int* ptr); // 需要 int* long v11; process(&amp;v11); // 错误!类型不匹配 ``` 4. **JNI 特殊限制** 在 `JNI_OnLoad` 等函数中谨慎使用: ```c JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env; vm-&gt;GetEnv((void**)&amp;env, JNI_VERSION_1_6); // 正确获取 env 地址 } ``` #### 5. **调试技巧** 当遇到 `&amp;v11` 相关问题时: 1. 使用 GDB/LLDB 打印地址值:`p/x &amp;v11` 2. 检查内存布局:`x/8wx &amp;v11` 3. 验证 JNI 引用有效性: ```c if (env-&gt;IsSameObject(v11, NULL)) { // 处理空引用 } ``` ### 相关问题 1. JNI 中如何正确处理全局引用和局部引用? 2. 在 JNI 中如何安全传递对象指针到不同线程? 3. JNI 类型签名(如 `&quot;Ljava/lang/String;&quot;`)的具体规则是什么? 4. 如何解析混淆后的 JNI 代码中的变量命名规律? [^1]:【NDK系列11】Java和c++交互 Jni初识。JNI是一种本地编程接口。它允许运行在JAVA虚拟机中的JAVA代码和用其他编程语言,诸如C语言、C++、汇编,应用和库之间的交互操作。 [^2]:查看Android源码中JNINativeMethod结构体 Android源码查看 由三个指针组成:第一个为函数名字、第二个为函数签名、第三个为本地函数地址。 [^3]:JNINDK开发(5)_使用Obfuscator-LLVM混淆JNI代码。2、使用IDA查看不混淆so文件 jstring __fastcall Java_com_rzr_llvm_MainActivity_stringFromJNI(JNIEnv *env, jobject a2) { ... std::string::string(&amp;v6, &quot;Hello from C++&quot;, &amp;__a); ... }。 [^4]:android /inputmanager-jni,Android InputManager分析。// mFirstPostImeInputStage = earlyPostImeStage; new EarlyPostImeInputStage(nativePostImeStage);
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值