NDK详细认识一下

NDK介绍

1.app为什么会把代码放到so中

c语言历史悠久,有很多现成的代码可用

c语言代码执行效率比java高

java代码很容易被反编译,而且反编译以后的逻辑很清晰

2.为什么要学习NDK开发

在安卓的so开发中,其他基本与c/c++开发一致,而与java交互需要用到ini

在本部分的DNK开发讲解中,主要就是介绍jni相关内容

so中会接触到的:系统库函数,jni调用,加密算法,魔改算法,系统调用,自定义算法

3.什么是JNI

jni是java Native intereface的缩写,从java1.1开始,jni标准成为java平台的一部分,允许Java代码和其他语言写的代码进行交互

JNI(Java Native Interface)是一个编程框架,允许 Java 代码与其他编程语言(通常是 C 或 C++)编写的本地代码进行交互。JNI 是 Java 平台的一部分,提供了一种机制,使得 Java 应用程序能够调用和被本地应用程序或库调用。

### JNI 的主要功能

1. **跨语言调用**:
   - 允许 Java 程序调用用 C/C++ 编写的函数和库,反之亦然。这使得开发者可以利用已有的 C/C++ 代码库,或在性能要求高的场景中使用本地代码。

2. **性能优化**:
   - 在需要高性能的计算密集型任务(如图形处理、音频处理和算法实现)时,使用本地代码可以显著提高性能。

3. **访问底层系统资源**:
   - JNI 允许 Java 代码访问底层操作系统功能和硬件资源,这在某些情况下是 Java 本身无法直接实现的。

### JNI 的工作原理

1. **Java 代码调用本地方法**:
   - 在 Java 中定义一个本地方法(native method),并使用 `native` 关键字进行声明。
   - Java 虚拟机(JVM)负责加载本地库,并通过 JNI 调用相应的本地实现。

2. **本地代码实现**:
   - 在 C/C++ 中实现与 Java 中定义的本地方法相对应的函数。JNI 提供了丰富的 API,允许本地代码访问 Java 对象、调用 Java 方法等。

3. **数据类型转换**:
   - JNI 处理 Java 数据类型与 C/C++ 数据类型之间的转换,例如将 Java 字符串转换为 C 字符串,或将 Java 数组转换为 C 数组。

### 示例

以下是一个简单的 JNI 使用示例:

1. **Java 代码**:

```java
public class HelloJNI {
    // 声明本地方法
    public native String sayHello();

    static {
        // 加载本地库
        System.loadLibrary("hello");
    }

    public static void main(String[] args) {
        HelloJNI helloJNI = new HelloJNI();
        System.out.println(helloJNI.sayHello());
    }
}
  1. C/C++ 实现
#include <jni.h>
#include <stdio.h>
#include "HelloJNI.h" // 生成的头文件

JNIEXPORT jstring JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject obj) {
    return (*env)->NewStringUTF(env, "Hello from C!");
}
  1. 编译和运行
    • 使用 javac 编译 Java 代码,使用 javah 生成头文件,使用 C 编译器编译 C 代码并生成共享库,最后运行 Java 程序。

注意事项

  • 性能:虽然 JNI 可以提高性能,但频繁的 Java 和本地代码之间的调用可能会导致性能下降,因此应谨慎使用。
  • 平台依赖性:使用 JNI 可能会使应用程序变得平台依赖,因为本地代码需要针对特定操作系统和硬件架构进行编译。
  • 错误处理:处理 JNI 代码时,需要特别注意内存管理和错误处理,以避免内存泄漏和程序崩溃。

总结

JNI 是 Java 平台中一个强大的功能,允许 Java 与本地代码进行交互,提供了跨语言调用的能力。它在性能优化和访问底层资源方面非常有用,但也带来了额外的复杂性和平台依赖性。开发者在使用 JNI 时需要权衡其优缺点,以确保应用程序的稳定性和性能。


# 4.什么是NDK

交叉编译工具链

DNK的配置

DNK,Cmake,LLDB的作用

```jsx
 “NDK”(Native Development Kit)。
 NDK 是 Android 开发中的一个重要工具包,允许开发者使用 C 和 C++ 等本地编程语言来编写 Android 应用程序的部分代码。

### NDK(Native Development Kit)

#### 定义
NDK 是 Android 提供的一个工具包,允许开发者使用 C 和 C++ 语言编写 Android 应用的本地代码。这对于需要高性能计算或需要访问底层硬件的应用程序(如游戏、图形处理、音频处理等)尤其有用。

#### 主要功能
- **性能优化**:通过使用 C/C++,开发者可以更好地控制内存和性能,适合计算密集型任务。
- **代码重用**:可以重用现有的 C/C++ 代码库,减少开发时间。
- **跨平台支持**:NDK 支持多种硬件架构(如 ARM、x86),使得应用可以在不同设备上运行。

#### 主要组件
- **工具链**:包括编译器、链接器等工具,用于构建本地代码。
- **JNI(Java Native Interface)**:允许 Java 代码与本地代码进行交互,使得两者可以相互调用。
- **示例和文档**:提供了丰富的示例代码和文档,帮助开发者快速上手。

#### 使用场景
- **游戏开发**:许多游戏引擎(如 Unity 和 Unreal Engine)使用 NDK 来提高性能。
- **图形和音频处理**:需要高效处理图形和音频数据的应用。
- **已有 C/C++ 代码库的集成**:将现有的 C/C++ 库集成到 Android 应用中。

### 总结
NDK 是一个强大的工具,适用于对性能有高要求的 Android 应用开发。它允许开发者利用 C/C++ 的优势,同时与 Java 代码进行无缝集成。如果你指的不是 NDK,请提供更多上下文,以便我更准确地回答你的问题。

5.ABI与指令集

ABI(应用二进制接口)和指令集是计算机体系结构和软件开发中的两个重要概念,它们在系统的运行和兼容性方面起着关键作用。以下是它们的定义、区别以及相互关系。

### 1. ABI(应用二进制接口)

**定义**:
ABI 是应用程序与操作系统之间的接口规范,定义了程序如何与操作系统进行交互。它包括了函数调用约定、数据类型的大小和对齐、系统调用的接口、库的使用规则等。

**主要组成部分**:
- **调用约定**:定义了函数参数如何传递(通过寄存器还是栈)、返回值如何处理等。
- **数据类型**:定义了基本数据类型(如整型、浮点型)的大小、对齐方式等。
- **系统调用接口**:定义了应用程序如何请求操作系统服务(如文件操作、网络通信等)。
- **二进制格式**:定义了可执行文件和库的格式(如 ELF、PE 等)。

**重要性**:
- 确保不同编译器生成的代码能够互操作。
- 使得程序可以在不同版本的操作系统上运行,只要 ABI 保持不变。

### 2. 指令集

**定义**:
指令集是处理器的基础,定义了处理器可以执行的所有指令的集合。每条指令指定了处理器如何执行特定的操作,如算术运算、数据传输、控制流等。

**主要组成部分**:
- **指令格式**:定义了每条指令的二进制编码格式。
- **操作码**:指示指令的类型(如加法、减法、跳转等)。
- **操作数**:指令所需的输入数据,可以是寄存器、内存地址或立即数。

**重要性**:
- 决定了处理器的功能和性能。
- 不同的处理器架构(如 x86、ARM、RISC-V 等)有不同的指令集。

### 3. ABI 与指令集的关系

- **依赖关系**:ABI 通常是建立在特定指令集之上的。ABI 定义了如何使用指令集来实现高层次的功能,如函数调用和数据管理。
- **兼容性**:如果一个新的处理器架构使用相同的指令集并保持 ABI 不变,那么用该 ABI 编写的程序可以在新的处理器上运行,而无需重新编译。
- **跨平台开发**:在跨平台开发中,开发者需要关注 ABI,以确保代码在不同平台上能够正确运行。指令集则影响代码的性能和效率。

### 4. 示例

- **ABI 示例**:
  - 在 x86 架构上,常见的 ABI 如 System V ABI(用于 UNIX/Linux 系统)和 Microsoft x64 ABI(用于 Windows 系统)。它们定义了函数参数的传递方式、返回值的处理等。

- **指令集示例**:
  - x86 指令集包括指令如 `ADD`(加法)、`SUB`(减法)、`MOV`(数据传输)等。
  - ARM 指令集则包括 `ADD`、`SUB`、`LDR`(加载寄存器)等指令。

### 总结

ABI 和指令集在计算机体系结构和软件开发中扮演着重要角色。ABI 定义了应用程序与操作系统之间的接口,而指令集则定义了处理器可以执行的指令。理解它们之间的关系有助于开发高效、兼容的应用程序。

6.NDK与java的区别

NDK(Native Development Kit)和 Java 是 Android 开发中的两个不同层面,主要区别如下:

1. 编程语言

  • NDK: 使用 C/C++ 语言进行开发,允许开发者编写本地代码。
  • Java: 使用 Java 语言进行开发,Android 应用的主要编程语言。

2. 性能

  • NDK: 本地代码通常比 Java 代码执行更快,适合性能要求高的应用,如游戏、图形处理和计算密集型任务。
  • Java: 虽然 Java 代码的性能较低,但其运行时环境(JVM)提供了自动内存管理和垃圾回收,简化了开发过程。

3. 开发复杂性

  • NDK: 开发过程相对复杂,需要处理内存管理、指针等底层细节,调试和错误处理也更为困难。
  • Java: 开发相对简单,提供了丰富的库和框架,易于学习和使用。

4. 访问 Android API

  • NDK: 通过 JNI(Java Native Interface)与 Java 代码交互,访问 Android API 需要额外的步骤。
  • Java: 直接访问 Android API,使用更为方便。

5. 应用场景

  • NDK: 适用于需要高性能计算、图形处理、音频处理等场景,如游戏引擎、图像处理库等。
  • Java: 适用于大多数 Android 应用开发,尤其是业务逻辑、用户界面等。

6. 代码可移植性

  • NDK: 由于使用 C/C++,需要针对不同平台编译不同的二进制文件,移植性较差。
  • Java: Java 代码可以在任何支持 JVM 的平台上运行,具有较好的可移植性。

7. 生态系统

  • NDK: 生态系统相对较小,主要依赖于 C/C++ 的库和工具。
  • Java: 拥有丰富的库、框架和社区支持,开发资源丰富。

总结

NDK 和 Java 各有优缺点,选择使用哪种方式取决于应用的需求和开发者的熟悉程度。对于性能要求高的部分,可以使用 NDK,而大部分业务逻辑和用户界面则可以使用 Java。

7.第一个NDK工程

1.CMakelist介绍


CMakeLists.txt 是 CMake 构建系统的配置文件,用于定义项目的构建过程。CMake 是一个跨平台的构建工具,能够生成标准的构建文件(如 Makefile 或 Visual Studio 项目文件)。以下是对 CMakeLists.txt 的详细介绍,包括基本结构、常用命令和示例。

1. 基本结构

CMakeLists.txt 文件通常位于项目的根目录中。其基本结构如下:

cmake_minimum_required(VERSION <version>)
project(<project_name>)

# 设置编译选项
set(CMAKE_CXX_STANDARD 11)

# 添加源文件
add_executable(<target_name> <source_files>)

2. 常用命令

  • cmake_minimum_required(VERSION <version>): 指定所需的最低 CMake 版本。
  • project(<project_name>): 定义项目的名称。
  • set(<variable> <value>): 设置变量的值。
  • add_executable(<target_name> <source_files>): 创建一个可执行文件目标。
  • add_library(<target_name> <source_files>): 创建一个库目标(静态库或动态库)。
  • target_link_libraries(<target> <libraries>): 指定目标链接的库。
  • include_directories(<directories>): 添加头文件搜索路径。
  • find_package(<package_name>): 查找并配置外部库。

3. 示例

以下是一个简单的 CMakeLists.txt 示例,展示如何构建一个包含多个源文件的 C++ 项目:

cmake_minimum_required(VERSION 3.10)  # 指定 CMake 最低版本
project(MyProject)                     # 定义项目名称

set(CMAKE_CXX_STANDARD 11)            # 设置 C++ 标准为 C++11

# 添加头文件搜索路径
include_directories(include)

# 添加源文件
set(SOURCES
    src/main.cpp
    src/other.cpp
)

# 创建可执行文件
add_executable(MyExecutable ${SOURCES})

# 链接外部库(例如 pthread)
find_package(Threads REQUIRED)
target_link_libraries(MyExecutable Threads::Threads)

4. 代码解析

  • cmake_minimum_required: 确保使用的 CMake 版本不低于 3.10。
  • project: 定义项目名称为 MyProject
  • set(CMAKE_CXX_STANDARD 11): 设置 C++ 标准为 C++11。
  • include_directories: 添加 include 目录作为头文件搜索路径。
  • set(SOURCES ...): 定义源文件列表。
  • add_executable: 创建名为 MyExecutable 的可执行文件,包含指定的源文件。
  • find_package: 查找并链接 pthread 库。

5. 生成构建文件

要使用 CMake 生成构建文件,通常在项目根目录下执行以下命令:

mkdir build
cd build
cmake ..
make

这将创建一个 build 目录,生成构建文件并编译项目。

6. 总结

CMakeLists.txt 是 CMake 的核心配置文件,定义了项目的构建过程。通过使用 CMake,开发者可以轻松管理跨平台的构建过程,简化项目的构建和依赖管理。

2.so的加载

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在 Android 和其他基于 Linux 的系统中,.so 文件(共享对象文件)是动态链接库的格式。它们可以在运行时被加载和使用,允许多个程序共享相同的库代码。

1. .so 文件的加载

在 Android 应用中,.so 文件通常通过 Java Native Interface (JNI) 加载。使用 System.loadLibrary() 方法可以在 Java 代码中加载 .so 文件。

1.1 使用 System.loadLibrary()

在 Java 中加载 .so 文件的基本方法如下:

public class NativeLib {
    static {
        System.loadLibrary("yourlib"); // 加载名为 libyourlib.so 的共享库
    }

    // 声明本地方法
    public native void yourNativeMethod();
}

  • System.loadLibrary("yourlib"): 这个调用会加载名为 libyourlib.so 的共享库。注意,这里不需要加前缀 lib 和后缀 .so

2. 动态链接库的基本概念

2.1 导出表和导入表

  • 导出表: 导出表包含了动态链接库中可供其他程序使用的函数和变量的列表。每个函数在导出表中都有一个名称和地址,使得其他程序可以通过这些信息调用该函数。
  • 导入表: 导入表是一个程序在运行时需要调用的外部函数的列表。它包含了程序需要使用的动态链接库的名称和其中函数的名称。程序在加载时会根据导入表的信息查找相应的共享库。

3. CMakeLists.txt

在 Android 项目中,通常使用 CMake 构建 .so 文件。CMakeLists.txt 是 CMake 的配置文件,指定了如何构建项目。

3.1 示例 CMakeLists.txt

cmake_minimum_required(VERSION 3.4.1)

# 设置库的名称
add_library(yourlib SHARED
            yourlib.cpp)

# 查找 JNI 库
find_library(log-lib log)

# 链接库
target_link_libraries(yourlib
                      ${log-lib})

  • add_library(yourlib SHARED yourlib.cpp): 创建一个名为 yourlib 的共享库,源文件为 yourlib.cpp
  • find_library(log-lib log): 查找 Android 的日志库。
  • target_link_libraries(yourlib ${log-lib}): 将日志库链接到 yourlib

4. 动态注册与静态注册

在 JNI 中,注册本地方法有两种方式:静态注册和动态注册。

4.1 静态注册

静态注册是在 Java 代码中直接声明本地方法,并在加载共享库时通过 JNIEXPORT 和 JNI_METHOD 定义方法的实现。通常在 .cpp 文件中实现。

JNIEXPORT void JNICALL Java_NativeLib_yourNativeMethod(JNIEnv *env, jobject obj) {
    // 实现
}

4.2 动态注册

动态注册是在运行时使用 JNI 提供的函数注册本地方法。这种方式通常在 JNI_OnLoad 函数中进行。

JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env;
    if (vm->GetEnv((void **)&env, JNI_VERSION_1_6) != JNI_OK) {
        return -1;
    }

    // 注册本地方法
    JNINativeMethod methods[] = {
        {"yourNativeMethod", "()V", (void *)yourNativeMethod}
    };

    jclass clazz = env->FindClass("NativeLib");
    if (clazz == nullptr) {
        return -1;
    }

    if (env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
        return -1;
    }

    return JNI_VERSION_1_6;
}

5. 总结

在 Android 和 Linux 中,.so 文件是动态链接库的格式,通过 JNI 可以在 Java 中加载和使用这些库。理解动态链接库的导出表和导入表、CMake 的使用,以及 JNI 的注册方式(静态和动态)对于开发高效的 Android 应用至关重要。

3.native函数的声明

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在 Java 中,声明本地(native)函数的语法非常简单。

1. Native 函数的声明

在 Java 类中,使用 native 关键字来声明本地方法。以下是基本的语法:

public class YourClass {
    // 声明一个本地方法
    public native void yourNativeMethod();
}

2. 关键点

  • native 关键字: 表示该方法的实现是在本地代码(如 C/C++)中,而不是在 Java 中实现的。
  • 方法签名: 本地方法的签名可以包含参数和返回类型。例如:
public native int add(int a, int b);

3. 示例

以下是一个完整的示例,展示如何声明和实现一个 native 方法。

3.1 Java 代码

public class NativeLib {
    // 声明本地方法
    public native int add(int a, int b);

    // 加载本地库
    static {
        System.loadLibrary("native-lib"); // 加载名为 libnative-lib.so 的共享库
    }

    public static void main(String[] args) {
        NativeLib lib = new NativeLib();
        int result = lib.add(5, 10);
        System.out.println("Result: " + result);
    }
}

3.2 C/C++ 代码

接下来,在 C/C++ 中实现这个本地方法:

#include <jni.h>
#include "NativeLib.h" // 生成的头文件

JNIEXPORT jint JNICALL Java_NativeLib_add(JNIEnv *env, jobject obj, jint a, jint b) {
    return a + b; // 返回两个整数的和
}

4. 生成头文件

在实现本地方法之前,需要生成 JNI 头文件。可以使用 javah 工具(在 JDK 8 及之前的版本)或使用 javac-h 选项(在 JDK 8 及之后的版本):

javac -h . NativeLib.java

这将生成一个名为 NativeLib.h 的头文件,其中包含本地方法的 C/C++ 函数原型。

5. 总结

  • 声明: 使用 native 关键字在 Java 中声明本地方法。
  • 实现: 在 C/C++ 中实现这些方法,并确保方法签名与 Java 中的声明一致。
  • 加载: 使用 System.loadLibrary() 加载包含本地方法实现的共享库。

通过这种方式,Java 应用程序可以利用本地代码的性能和功能。

4.JNI函数的静态注册规则

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在 JNI 中,静态注册是指在 Java 类中直接声明本地方法,并在加载共享库时通过 JNIEXPORT 和 JNI_METHOD 定义方法的实现。

1. 静态注册的基本规则

  1. 方法声明: 在 Java 类中声明本地方法,使用 native 关键字。
  2. JNIEXPORT 和 JNICALL: 在 C/C++ 中实现这些方法时,必须使用 JNIEXPORTJNICALL 宏来定义方法的导出。
  3. 方法签名: 方法的名称和参数类型必须与 Java 中的声明严格匹配,包括返回类型和参数的顺序。
  4. 命名约定: 本地方法的 C/C++ 实现名称必须按照特定的命名约定进行命名,格式为 Java_包名_类名_方法名

2. 静态注册的步骤

2.1 Java 代码

首先,在 Java 中声明本地方法。例如,创建一个名为 NativeLib 的类:

public class NativeLib {
    // 声明本地方法
    public native void nativeMethod();

    // 加载本地库
    static {
        System.loadLibrary("native-lib");
    }
}

2.2 C/C++ 代码

接下来,在 C/C++ 中实现这个本地方法。假设类的包名为 com.example,类名为 NativeLib,方法名为 nativeMethod,那么 C/C++ 中的实现如下:

#include <jni.h>
#include "com_example_NativeLib.h" // 生成的头文件

JNIEXPORT void JNICALL Java_com_example_NativeLib_nativeMethod(JNIEnv *env, jobject obj) {
    // 实现本地方法
    printf("Hello from native method!\\n");
}

3. 生成头文件

在实现本地方法之前,需要生成 JNI 头文件。使用以下命令:

javac -h . NativeLib.java

这将生成一个名为 com_example_NativeLib.h 的头文件,其中包含本地方法的 C/C++ 函数原型。

4. 编译和链接

编译 C/C++ 代码并生成共享库。例如,使用 GCC 编译:

gcc -shared -o libnative-lib.so -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" com_example_NativeLib.c

5. 注意事项

  • JNIEXPORT: 确保使用 JNIEXPORT,这使得方法可以被 JNI 识别。
  • JNICALL: 确保使用 JNICALL,这是调用约定,确保方法的参数传递方式正确。
  • 方法签名: 方法的名称和参数必须完全匹配,包括大小写。

6. 总结

静态注册是 JNI 中一种简单直接的方法,适用于本地方法的实现。通过遵循上述规则和步骤,可以确保本地方法能够正确地与 Java 代码进行交互。

5.JNIEnv,jobject/jclass

在 Java Native Interface (JNI) 中,JNIEnvjobjectjclass 是与 Java 虚拟机(JVM)交互时使用的重要数据类型。下面是对它们的详细解释:

  1. JNIEnv
  • 定义JNIEnv 是一个指向结构体的指针,该结构体包含了一组函数指针,这些函数用于与 JVM 进行交互。

  • 作用:它提供了访问 JNI 函数的上下文,包括调用 Java 方法、访问 Java 字段、创建 Java 对象等。

  • 使用:在 JNI 函数中,JNIEnv 是第一个参数,通常命名为 env。通过它,可以调用各种 JNI 函数。例如:

    jstring result = env->NewStringUTF("Hello from C++");
    
    
  1. jobject
  • 定义jobject 是一个代表 Java 对象的引用类型。它可以指向任何 Java 对象。

  • 作用:在 JNI 方法中,jobject 通常用来表示调用本地方法的 Java 对象实例。通过这个引用,可以访问该对象的字段和方法。

  • 使用:在 JNI 函数中,jobject 通常是第二个参数,通常命名为 obj。例如:

    jclass clazz = env->GetObjectClass(obj);
    
    
  1. jclass
  • 定义jclass 是一个代表 Java 类的引用类型,专门用于表示 Java 类的类型。

  • 作用:在 JNI 中,jclass 用于获取类的信息、访问类的静态方法和字段等。

  • 使用:在 JNI 函数中,jclass 通常是通过 GetObjectClassFindClass 函数获得的。例如:

    jclass clazz = env->GetObjectClass(obj);
    jmethodID methodId = env->GetMethodID(clazz, "methodName", "()V");
    
    

示例

下面是一个简单的 JNI 示例,演示了如何使用 JNIEnvjobjectjclass

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

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapp_MainActivity_stringFromJNI(JNIEnv *env, jobject obj) {
    // 获取调用该方法的对象的类
    jclass clazz = env->GetObjectClass(obj);

    // 创建一个新的字符串
    const char *message = "Hello from C++";
    jstring result = env->NewStringUTF(message);

    // 返回字符串
    return result;
}

总结

  • JNIEnv:提供与 JVM 交互的接口,允许调用 JNI 函数。
  • jobject:代表调用本地方法的 Java 对象实例,允许访问该对象的字段和方法。
  • jclass:代表 Java 类,允许访问类的静态方法和字段。

通过这些类型,JNI 能够实现 Java 和 C/C++ 之间的高效交互。

6.NewstringUTF

系统函数(使用频率很高)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

NewStringUTF 是 Java Native Interface (JNI) 中的一个函数,用于创建一个新的 Java 字符串对象。它接受一个 C 风格的字符串(即以 null 结尾的字符数组)并将其转换为 Java 中的 String 对象。以下是关于 NewStringUTF 的详细说明:

函数原型

jstring NewStringUTF(JNIEnv *env, const char *utf);

参数

  • JNIEnv *env:指向 JNI 环境的指针,提供了与 JVM 交互的接口。
  • const char *utf:一个指向以 null 结尾的 C 字符串的指针,通常是 UTF-8 编码的字符串。

返回值

  • jstring:返回一个新的 Java 字符串对象。如果发生错误,可能返回 NULL

使用场景

NewStringUTF 通常用于以下情况:

  1. 从 C/C++ 代码返回字符串到 Java:当需要将 C/C++ 中的字符串传递回 Java 层时,可以使用此函数。
  2. 创建字符串对象:在需要构造 Java 字符串的情况下,例如在 JNI 方法中返回一些文本信息。

示例

下面是一个简单的示例,演示如何在 JNI 中使用 NewStringUTF

#include <jni.h>

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapp_MainActivity_stringFromJNI(JNIEnv *env, jobject obj) {
    // C 风格的字符串
    const char *message = "Hello from C++";

    // 创建一个新的 Java 字符串对象
    jstring result = env->NewStringUTF(message);

    // 返回 Java 字符串
    return result;
}

注意事项

  1. 内存管理NewStringUTF 创建的 Java 字符串对象是由 JVM 管理的,因此不需要手动释放。只需确保在不再需要该字符串时,JVM 会自动处理内存。
  2. 编码:确保传递给 NewStringUTF 的字符串是有效的 UTF-8 编码。如果字符串包含无效的 UTF-8 字符,可能会导致异常或未定义的行为。
  3. 性能:频繁调用 NewStringUTF 可能会影响性能,尤其是在高频率的 JNI 调用中,建议在可能的情况下减少字符串的创建。

总结

NewStringUTF 是一个重要的 JNI 函数,用于在 C/C++ 代码中创建 Java 字符串对象。通过使用这个函数,可以轻松地将 C/C++ 中的文本信息传递给 Java 层。

7.在DNK开发中,一定要注意那些是java的类型,那些是c/c++的类型,在适当的时候需要转换

在 JNI(Java Native Interface)开发中,确实需要注意 Java 和 C/C++ 之间的数据类型差异。不同的语言有不同的类型系统,因此在两者之间进行交互时,必须进行适当的转换。以下是一些常见的 Java 类型和对应的 C/C++ 类型,以及如何进行转换的示例。

常见的 Java 和 C/C++ 类型对照

Java 类型C/C++ 类型说明
intjint32 位整数
longjlong64 位整数
floatjfloat32 位浮点数
doublejdouble64 位浮点数
booleanjboolean1 字节布尔值(0 或 1)
charjchar16 位 Unicode 字符
bytejbyte8 位字节
shortjshort16 位整数
StringjstringJava 字符串对象
ObjectjobjectJava 对象引用
ClassjclassJava 类引用
数组jobjectArrayJava 数组对象

示例:类型转换

  1. 整数类型转换
#include <jni.h>

extern "C" JNIEXPORT void JNICALL
Java_com_example_myapp_MainActivity_nativeMethod(JNIEnv *env, jobject obj, jint intValue) {
    // 将 jint 转换为 C/C++ 的 int
    int cIntValue = static_cast<int>(intValue);

    // 进行一些操作
    cIntValue += 10;

    // 将结果转换回 jint
    jint result = static_cast<jint>(cIntValue);
}

  1. 字符串类型转换
#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapp_MainActivity_stringFromJNI(JNIEnv *env, jobject obj) {
    // 创建一个 C++ 字符串
    std::string cppString = "Hello from C++";

    // 将 C++ 字符串转换为 Java 字符串
    jstring javaString = env->NewStringUTF(cppString.c_str());

    return javaString; // 返回 Java 字符串
}

  1. 数组类型转换
#include <jni.h>

extern "C" JNIEXPORT jintArray JNICALL
Java_com_example_myapp_MainActivity_getArray(JNIEnv *env, jobject obj) {
    // 创建一个 C++ 数组
    int cArray[] = {1, 2, 3, 4, 5};
    jintArray javaArray = env->NewIntArray(5); // 创建 Java 整数数组

    // 将 C++ 数组复制到 Java 数组中
    env->SetIntArrayRegion(javaArray, 0, 5, cArray);

    return javaArray; // 返回 Java 数组
}

注意事项

  1. 内存管理:所有通过 JNI 创建的 Java 对象(如字符串、数组等)都由 JVM 管理,因此不需要手动释放它们的内存。确保在不再需要时,JVM 会自动处理这些对象的内存。
  2. 编码:在处理字符串时,确保使用有效的 UTF-8 编码。NewStringUTF 函数会将 C 字符串转换为 Java 字符串,但要确保输入是有效的 UTF-8 字符串。
  3. 错误处理:在 JNI 调用过程中,可能会发生错误(例如,找不到类、方法或字段)。可以使用 env->ExceptionCheck()env->ExceptionClear() 来检查和清除异常。
  4. 性能:频繁的类型转换和 JNI 调用可能会影响性能。尽量在本地代码中处理更多逻辑,减少 JNI 调用的频率。

总结

在 JNI 开发中,理解 Java 和 C/C++ 之间的数据类型差异非常重要。通过适当的转换,可以确保在两者之间顺利地传递数据。务必注意内存管理、编码和错误处理,以确保代码的健壮性和性能。

8.extern “C” JNIEXPORT jstring JNICALL

extern "C" JNIEXPORT jstring JNICALL 是在 Java Native Interface (JNI) 中定义本地方法的一种方式。下面是对这个声明的逐部分解释:

  1. extern "C"
  • 作用:这个关键字告诉编译器使用 C 语言的链接方式,而不是 C++ 的链接方式。C++ 会对函数名进行“名称修饰”(name mangling),使得函数名在编译后变得复杂,而 C 则不会。
  • 目的:确保在 C++ 中定义的函数能够被 C 代码(如 Java 的 JNI)正确地调用。使用 extern "C" 可以避免名称修饰,从而使得 JNI 能够找到这些函数。
  1. JNIEXPORT
  • 作用:这是一个宏,通常在 jni.h 头文件中定义。它用于指定函数的导出属性,使得该函数能够被 JNI 调用。
  • 目的:确保该函数在动态链接库(.so 文件或 DLL 文件)中是可见的,以便 Java 虚拟机(JVM)能够找到并调用它。
  1. jstring
  • 作用:这是 JNI 中定义的一种数据类型,表示一个 Java 字符串对象。在 JNI 中,jstring 是一个表示 Java 字符串的指针类型。
  • 目的:作为返回值类型,表示这个函数将返回一个 Java 字符串对象。
  1. JNICALL
  • 作用:这是一个宏,定义了调用约定。它确保函数按照 JNI 规定的方式进行调用。
  • 目的:提供跨平台的兼容性,确保在不同的操作系统和编译器中,函数调用的行为是一致的。

总结

将这些部分结合起来,extern "C" JNIEXPORT jstring JNICALL 定义了一个 JNI 函数,该函数可以被 Java 调用,返回一个 Java 字符串,并且使用 C 的链接方式和 JNI 的调用约定。以下是一个示例函数的完整声明:

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapp_MainActivity_stringFromJNI(JNIEnv *env, jobject obj) {
    return env->NewStringUTF("Hello from C++");
}

在这个示例中,Java_com_example_myapp_MainActivity_stringFromJNI 是 JNI 函数的名称,它遵循 JNI 的命名规则,表示这个函数是 MainActivity 类中的 stringFromJNI 方法。这个函数使用 JNIEnv 指针来与 Java 虚拟机交互,并返回一个新的 Java 字符串。

9.指定只编译arm64的so

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

要指定只编译 ARM64 架构的 .so 文件(共享库),你需要在 Android NDK 项目中配置 CMake 或 ndk-build 文件。以下是如何在这两种构建系统中进行配置的示例。

1. 使用 CMake

如果你使用 CMake 构建系统,可以在 CMakeLists.txt 文件中指定目标架构。

1.1 CMakeLists.txt 示例

cmake_minimum_required(VERSION 3.4.1)

# 设置库的名称
add_library(yourlib SHARED yourlib.cpp)

# 设置目标架构为 ARM64
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DANDROID_ABI=arm64-v8a")

# 查找 JNI 库
find_library(log-lib log)

# 链接库
target_link_libraries(yourlib ${log-lib})

1.2 通过 gradle 配置

build.gradle 文件中,你可以指定 ABI 过滤器:

android {
    ...
    defaultConfig {
        ...
        ndk {
            abiFilters "arm64-v8a" // 只编译 ARM64
        }
    }
}

2. 使用 ndk-build

如果你使用 ndk-build,可以在 Android.mk 文件中指定目标架构。

2.1 Android.mk 示例

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := yourlib
LOCAL_SRC_FILES := yourlib.cpp

include $(BUILD_SHARED_LIBRARY)

# 指定只编译 ARM64
APP_ABI := arm64-v8a

2.2 Application.mk 示例

Application.mk 文件中,你可以指定 ABI:

APP_ABI := arm64-v8a

3. 编译

无论你使用哪种方法,确保在 Android Studio 中同步项目并进行构建。构建完成后,生成的 .so 文件将仅针对 ARM64 架构。

4. 总结

通过在 CMake 或 ndk-build 配置文件中指定 arm64-v8a,你可以确保只编译 ARM64 架构的共享库。这对于优化应用程序的性能和减小 APK 大小非常有用。

10.指定编译后的so名字

要在Frida中主动调用特定的check函数,并指定编译后的共享库(.so文件)的名称,你可以使用Frida的Module API来加载该库并调用其中的函数。以下是一个示例,说明如何实现这一目标。

Frida 脚本示例

假设你要调用的check函数在名为libtarget.so的共享库中。以下是Frida脚本的实现:

// 指定要加载的共享库名称
var targetLibrary = 'libtarget.so';

function main() {
    // 等待目标库加载
    Module.ensureInitialized(targetLibrary);

    // 获取目标库中的check函数
    var checkFunction = Module.findExportByName(targetLibrary, 'check');

    if (checkFunction) {
        console.log('check function found at: ' + checkFunction);

        // 定义主动调用check函数的逻辑
        setInterval(function() {
            // 假设check函数接受三个参数,类型为int, float, string
            var arg1 = 42; // 示例int参数
            var arg2 = 3.14; // 示例float参数
            var arg3 = "test_flag"; // 示例string参数

            // 调用check函数
            var result = Memory.callFunction(checkFunction, [arg1, arg2, arg3]);
            console.log('check called with arguments: ' + arg1 + ', ' + arg2 + ', ' + arg3 + '. Result: ' + result);
        }, 2000); // 每2秒调用一次
    } else {
        console.error('check function not found in ' + targetLibrary);
    }
}

// 在Java环境中执行
Java.perform(main);

代码说明

  1. 指定共享库名称
    • var targetLibrary = 'libtarget.so';:指定要加载的共享库的名称。
  2. 确保库已加载
    • Module.ensureInitialized(targetLibrary);:确保目标库已加载到内存中。
  3. 查找函数地址
    • var checkFunction = Module.findExportByName(targetLibrary, 'check');:查找check函数的地址。
  4. 主动调用函数
    • 使用setInterval设置定时器,每2秒调用一次check函数。
    • Memory.callFunction(checkFunction, [arg1, arg2, arg3]);:调用check函数,传递示例参数。
  5. 输出结果
    • 打印调用的参数和返回结果。

运行Frida脚本

使用以下命令运行Frida脚本:

frida -U -l hook_check.js -f com.ctf.mobile --no-pause

注意事项

  • 确保check函数的参数类型和数量与实际实现匹配。在示例中,假设check接受一个int、一个float和一个string类型的参数。
  • 根据需要调整定时器的时间间隔(例如,2000毫秒)。
  • 监控控制台输出,以查看check函数的调用情况和返回结果。

8.so中常用的log输出

在使用 JNI(Java Native Interface)时,通常会在 C/C++ 代码中输出日志,以便于调试和监控。以下是一些常用的日志输出方法,特别是在 Android 开发中,常用的日志库是 Android 的 `Log` 类,但在 C/C++ 中,我们可以使用标准输出或其他日志库。

### 1. 使用 `printf`

最简单的方式是使用 `printf` 函数输出日志信息:

```c
#include <stdio.h>

JNIEXPORT void JNICALL Java_HelloJNI_logMessage(JNIEnv *env, jobject obj) {
    printf("Hello from C! This is a log message.\n");
}

2. 使用 LOGI, LOGE 等宏(在 Android NDK 中)

在 Android NDK 中,可以使用 __android_log_print 函数来输出日志。这个函数提供了不同的日志级别,如 LOGI(信息)、LOGE(错误)、LOGD(调试)等。

首先,包含头文件:

#include <android/log.h>

然后,可以使用以下方式输出日志:

#define LOG_TAG "MyNativeCode"

JNIEXPORT void JNICALL Java_HelloJNI_logMessage(JNIEnv *env, jobject obj) {
    __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Hello from C! This is an info log.");
    __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "This is an error log.");
    __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "This is a debug log.");
}

3. 日志级别说明

  • ANDROID_LOG_VERBOSE: 详细信息
  • ANDROID_LOG_DEBUG: 调试信息
  • ANDROID_LOG_INFO: 一般信息
  • ANDROID_LOG_WARN: 警告信息
  • ANDROID_LOG_ERROR: 错误信息
  • ANDROID_LOG_FATAL: 致命错误

4. 使用 C++ 的 std::cout

如果你在使用 C++,可以使用标准输出流:

#include <iostream>

extern "C" {
    JNIEXPORT void JNICALL Java_HelloJNI_logMessage(JNIEnv *env, jobject obj) {
        std::cout << "Hello from C++!" << std::endl;
    }
}

5. 组合使用 JNI 和日志输出

在 JNI 方法中,可以结合 Java 的日志系统和本地日志输出。例如,可以在本地代码中捕获错误并将其通过 JNI 返回到 Java 层进行处理。

示例代码

以下是一个完整的示例,展示了如何在 JNI 中使用日志输出:

#include <jni.h>
#include <android/log.h>

#define LOG_TAG "MyNativeCode"

JNIEXPORT jstring JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject obj) {
    __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "sayHello called");
    return (*env)->NewStringUTF(env, "Hello from C!");
}

JNIEXPORT void JNICALL Java_HelloJNI_logMessage(JNIEnv *env, jobject obj) {
    __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Logging from native code.");
}

总结

在 JNI 开发中,使用适当的日志输出机制可以帮助你快速定位问题和调试代码。在 Android NDK 开发中,推荐使用 __android_log_print 函数,以便更好地与 Android 的日志系统集成。使用标准输出或其他日志库也是可行的,但在 Android 环境中,使用 Android 提供的日志功能通常是最有效的方式。


在 Android 开发中,通常会在 JNI(Java Native Interface)中使用 C/C++ 代码,并希望能够在 Java 层通过日志输出进行调试。以下是一个示例,展示了如何在 C/C++ 代码中使用日志输出,并在 Java 代码中调用这些方法。

### 1. Java 代码示例

首先,在 Java 代码中,我们定义一个类来加载本地库,并调用本地方法。我们将使用 `Log.d()` 来输出调试信息。

```java
import android.util.Log;

public class HelloJNI {
    private static final String TAG = "HelloJNI";

    static {
        System.loadLibrary("hello"); // 加载本地库
    }

    // 声明本地方法
    public native String sayHello();
    public native void logMessage();

    public void testLogging() {
        Log.d(TAG, "Testing JNI logging.");
        String message = sayHello();
        Log.d(TAG, "Message from native code: " + message);
        logMessage();
    }
}

2. C/C++ 代码示例

在 C/C++ 代码中,我们使用 __android_log_print() 来输出日志信息。确保包含必要的头文件。

#include <jni.h>
#include <android/log.h>

#define LOG_TAG "HelloJNI"

JNIEXPORT jstring JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject obj) {
    __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "sayHello called from native code.");
    return (*env)->NewStringUTF(env, "Hello from C!");
}

JNIEXPORT void JNICALL Java_HelloJNI_logMessage(JNIEnv *env, jobject obj) {
    __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "logMessage called from native code.");
}

3. 使用示例

在你的 Android Activity 或其他地方调用 testLogging() 方法:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        HelloJNI helloJNI = new HelloJNI();
        helloJNI.testLogging(); // 调用测试日志的方法
    }
}

4. 日志输出示例

当你运行应用程序并调用 testLogging() 方法时,你应该能够在 Logcat 中看到如下输出:

D/HelloJNI: Testing JNI logging.
D/HelloJNI: sayHello called from native code.
D/HelloJNI: logMessage called from native code.
D/HelloJNI: Message from native code: Hello from C!

总结

通过在 Java 代码中使用 Log.d() 和在 C/C++ 代码中使用 __android_log_print(),你可以方便地输出调试信息。这对于调试 JNI 交互和确保本地代码正常工作非常有帮助。

9.NDK多线程

在 Android NDK 中实现多线程可以通过多种方式进行,最常见的方法是使用 POSIX 线程(pthread)。以下是一个关于如何在 NDK 中创建和管理多线程的基本示例。

1. 使用 POSIX 线程(pthread)

1.1. C/C++ 代码示例

首先,你需要在 C/C++ 代码中创建一个线程并进行简单的操作。以下是一个使用 pthread 库的示例:

#include <jni.h>
#include <pthread.h>
#include <android/log.h>
#include <unistd.h>

#define LOG_TAG "NDKExample"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

void* thread_function(void* arg) {
    LOGI("Thread started.");

    // 模拟一些工作
    for (int i = 0; i < 5; i++) {
        LOGI("Thread is running: %d", i);
        sleep(1); // 暂停1秒
    }

    LOGI("Thread finished.");
    return NULL;
}

JNIEXPORT void JNICALL Java_com_example_ndkexample_MainActivity_startThread(JNIEnv *env, jobject obj) {
    pthread_t thread;
    LOGI("Creating thread...");

    // 创建线程
    if (pthread_create(&thread, NULL, thread_function, NULL) != 0) {
        LOGI("Error creating thread.");
    }

    // 等待线程结束
    pthread_join(thread, NULL);
    LOGI("Thread has been joined.");
}

1.2. Java 代码示例

在 Java 代码中,你需要声明本地方法并调用它。以下是 Java 类的示例:

package com.example.ndkexample;

import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    static {
        System.loadLibrary("native-lib"); // 加载本地库
    }

    // 声明本地方法
    public native void startThread();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Log.d(TAG, "Starting thread...");
        startThread(); // 调用本地方法以启动线程
    }
}

2. 线程安全

在多线程环境中,确保数据的安全性是非常重要的。你可以使用互斥锁(mutex)来保护共享资源。以下是如何使用 pthread 的互斥锁的示例:

#include <pthread.h>
#include <android/log.h>

#define LOG_TAG "NDKExample"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

pthread_mutex_t lock;

void* thread_function(void* arg) {
    LOGI("Thread started.");

    // 访问共享资源
    pthread_mutex_lock(&lock);
    // 这里可以安全地访问共享资源
    LOGI("Thread is modifying shared resource.");
    pthread_mutex_unlock(&lock);

    LOGI("Thread finished.");
    return NULL;
}

JNIEXPORT void JNICALL Java_com_example_ndkexample_MainActivity_startThread(JNIEnv *env, jobject obj) {
    pthread_t thread;
    LOGI("Creating thread...");

    // 初始化互斥锁
    pthread_mutex_init(&lock, NULL);

    // 创建线程
    if (pthread_create(&thread, NULL, thread_function, NULL) != 0) {
        LOGI("Error creating thread.");
    }

    // 等待线程结束
    pthread_join(thread, NULL);
    LOGI("Thread has been joined.");

    // 销毁互斥锁
    pthread_mutex_destroy(&lock);
}

3. 线程池(可选)

如果你需要更复杂的线程管理,可以考虑实现一个线程池。线程池可以帮助你重用线程,减少线程创建和销毁的开销。

4. 总结

在 Android NDK 中使用多线程可以通过 POSIX 线程(pthread)实现。确保在多线程环境中使用适当的同步机制(如互斥锁)来保护共享资源,从而避免数据竞争和不一致性问题。通过合理设计线程的生命周期和管理,可以提高应用程序的性能和响应能力。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

10.pthread_create

是 POSIX 线程库中用于创建新线程的函数。它允许程序在运行时生成新的执行流,使得多线程编程成为可能。以下是对 pthread_create 的详细介绍,包括其用法、参数、返回值以及示例。

1. 函数原型

#include <pthread.h>

int pthread_create(pthread_t *thread,
										const pthread_attr_t *attr,
										void *(*start_routine)(void *), 
										void *arg);

2. 参数说明

  • pthread_t *thread: 指向 pthread_t 类型的指针,用于存储新创建线程的 ID。这个 ID 可以用于后续的线程管理(如 pthread_joinpthread_cancel)。
  • const pthread_attr_t *attr: 指向线程属性对象的指针。这个参数可以为 NULL,表示使用默认的线程属性。线程属性可以定义线程的栈大小、调度策略等。
  • void *(*start_routine)(void *): 指向线程执行的函数的指针。这个函数必须接受一个 void* 类型的参数,并返回一个 void* 类型的结果。线程开始执行时会调用这个函数。
  • void *arg: 传递给 start_routine 函数的参数。可以传递任何类型的数据,但通常需要将其转换为 void* 进行传递。

3. 返回值

  • 成功时: 返回 0。
  • 失败时: 返回一个非零的错误代码,表示错误的类型。例如,常见的错误包括:
    • EAGAIN: 系统资源不足,无法创建更多线程。
    • EINVAL: 传入的属性参数无效。
    • EPERM: 没有权限创建线程。

4. 示例代码

以下是一个使用 pthread_create 的简单示例,创建一个线程并执行一个函数:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define NUM_THREADS 5

void* thread_function(void* arg) {
    int thread_id = *((int*)arg);
    printf("Thread %d is running.\\n", thread_id);
    sleep(1); // 模拟一些工作
    printf("Thread %d is finished.\\n", thread_id);
    return NULL;
}

int main() {
    pthread_t threads[NUM_THREADS];
    int thread_ids[NUM_THREADS];

    // 创建多个线程
    for (int i = 0; i < NUM_THREADS; i++) {
        thread_ids[i] = i;
        if (pthread_create(&threads[i], NULL, thread_function, &thread_ids[i]) != 0) {
            perror("Failed to create thread");
            exit(EXIT_FAILURE);
        }
    }

    // 等待所有线程结束
    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_join(threads[i], NULL);
    }

    printf("All threads have finished.\\n");
    return 0;
}

5. 代码解析

  • 线程函数: thread_function 接受一个参数,打印线程 ID 并模拟工作。线程结束时返回 NULL
  • 创建线程: 在 main 函数中,使用 pthread_create 创建多个线程,每个线程执行 thread_function
  • 等待线程: 使用 pthread_join 等待所有线程完成。这样可以确保主线程在所有子线程完成之前不会退出。

6. 线程属性(可选)

如果需要自定义线程的行为,可以使用 pthread_attr_t 设置线程属性。例如,设置线程栈大小:

pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, 1024 * 1024); // 设置栈大小为1MB

pthread_create(&thread, &attr, thread_function, NULL);
pthread_attr_destroy(&attr); // 清理属性对象

7. 总结

pthread_create 是创建和管理线程的核心函数。通过合理使用这个函数,可以实现多线程应用程序,提高程序的并发性和性能。注意在多线程环境中要谨慎处理共享资源,以避免数据竞争和不一致性问题。

11.JNI_Load


在 Android NDK 中,JNI_OnLoad 是一个特殊的函数,用于在动态链接库(.so 文件)被加载时执行初始化操作。这个函数通常用于设置 JNI 环境、注册本地方法等。以下是一个详细的 JNI_OnLoad 示例,包括注释,帮助你理解每一部分的作用。

示例代码

#include <jni.h>
#include <android/log.h>

#define LOG_TAG "JNIExample"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

// 声明本地方法
JNIEXPORT void JNICALL Java_com_example_ndkexample_MainActivity_nativeMethod(JNIEnv *env, jobject obj) {
    LOGI("Native method called!");
}

//!!!!!!!!!!!!!!!!!!!!!!!
***// JNI_OnLoad 函数
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env;

    // 获取 JNI 环境
    if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) {
        LOGI("Failed to get JNI environment");
        return -1; // 返回错误代码
    }***
//!!!!!!!!!!!!!!!!!!!!!!!!!!
    // 注册本地方法
    jclass clazz = (*env)->FindClass(env, "com/example/ndkexample/MainActivity");
    if (clazz == NULL) {
        LOGI("Failed to find class");
        return -1; // 返回错误代码
    }

    // 定义本地方法的结构
    JNINativeMethod methods[] = {
        {"nativeMethod", "()V", (void *)Java_com_example_ndkexample_MainActivity_nativeMethod}
    };

    // 注册本地方法
    if ((*env)->RegisterNatives(env, clazz, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
        LOGI("Failed to register native methods");
        return -1; // 返回错误代码
    }

    LOGI("JNI_OnLoad completed successfully");
    return JNI_VERSION_1_6; // 返回 JNI 版本
}

代码解析

  1. 包含头文件:

    #include <jni.h>
    #include <android/log.h>
    
    
    • jni.h: 提供 JNI 的函数和数据类型定义。
    • android/log.h: 用于 Android 日志打印。
  2. 日志宏定义:

    #define LOG_TAG "JNIExample"
    #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
    
    
    • 定义日志标签和日志打印宏,方便在代码中输出日志信息。
  3. 本地方法实现:

    JNIEXPORT void JNICALL Java_com_example_ndkexample_MainActivity_nativeMethod(JNIEnv *env, jobject obj) {
        LOGI("Native method called!");
    }
    
    
    • 这是一个简单的本地方法实现,调用时会打印日志。
  4. JNI_OnLoad 函数:

    JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    
    
    • JNI_OnLoad 是 JNI 的入口点,当库被加载时会自动调用。
  5. 获取 JNI 环境:

    if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) {
        LOGI("Failed to get JNI environment");
        return -1; // 返回错误代码
    }
    
    
    • 通过 GetEnv 获取 JNI 环境指针 env,并检查是否成功。
  6. 查找 Java 类:

    jclass clazz = (*env)->FindClass(env, "com/example/ndkexample/MainActivity");
    if (clazz == NULL) {
        LOGI("Failed to find class");
        return -1; // 返回错误代码
    }
    
    
    • 使用 FindClass 查找 Java 类 MainActivity,如果找不到则返回错误。
  7. 定义本地方法数组:

    JNINativeMethod methods[] = {
        {"nativeMethod", "()V", (void *)Java_com_example_ndkexample_MainActivity_nativeMethod}
    };
    
    
    • 定义一个 JNINativeMethod 结构体数组,包含本地方法的名称、签名和实现。
  8. 注册本地方法:

    if ((*env)->RegisterNatives(env, clazz, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
        LOGI("Failed to register native methods");
        return -1; // 返回错误代码
    }
    
    
    • 使用 RegisterNatives 注册本地方法。如果注册失败,返回错误。
  9. 完成初始化:

    LOGI("JNI_OnLoad completed successfully");
    return JNI_VERSION_1_6; // 返回 JNI 版本
    
    
    • 打印成功信息并返回 JNI 版本。

结论

JNI_OnLoad 是 JNI 中一个重要的初始化函数,用于在动态库加载时执行必要的设置,如获取 JNI 环境、注册本地方法等。理解和正确实现这个函数是使用 JNI 进行 Android NDK 开发的基础。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

12.JavaVM

JavaVM 是 Java Native Interface (JNI) 中的一个重要概念,它表示 Java 虚拟机的一个实例。通过 JavaVM,本地代码可以与 Java 代码进行交互。下面是关于 JavaVM 的详细介绍和使用示例。

1. JavaVM 的定义

在 JNI 中,JavaVM 是一个指向 Java 虚拟机的指针,它允许本地代码访问 Java 的功能。通过 JavaVM,你可以:

  • 创建 Java 对象。
  • 调用 Java 方法。
  • 访问 Java 字段。

2. JavaVM 的结构

在 JNI 中,通常会使用一个 JavaVM 指针和一个 JNIEnv 指针。JNIEnv 是一个指向 JNI 环境的指针,提供了调用 JNI 函数的接口。每个线程可以有自己的 JNIEnv 指针,但通常只有一个 JavaVM 实例。

3. 使用 JavaVM 的示例

以下是一个简单的示例,展示如何在 C/C++ 中使用 JavaVM 来调用 Java 方法。

3.1 Java 代码

首先,我们定义一个简单的 Java 类,包含一个要调用的方法:

// HelloWorld.java
public class HelloWorld {
    public void sayHello() {
        System.out.println("Hello from Java!");
    }
}

编译这个 Java 文件:

javac HelloWorld.java

3.2 C/C++ 代码

接下来,我们编写 C/C++ 代码,使用 JNI 加载 Java 类并调用 sayHello 方法:

#include <jni.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    JavaVM *jvm;             // Java 虚拟机
    JNIEnv *env;            // JNI 环境
    JavaVMInitArgs vm_args; // JVM 启动参数
    JavaVMOption options[1]; // JVM 选项

    // 设置 JVM 选项,指定类路径
    options[0].optionString = "-Djava.class.path=."; // 当前目录
    vm_args.version = JNI_VERSION_1_6; // JNI 版本
    vm_args.nOptions = 1; // 选项数量
    vm_args.options = options; // 选项数组
    vm_args.ignoreUnrecognized = 0; // 忽略未识别的选项

    // 创建 Java 虚拟机
    jint ret = JNI_CreateJavaVM(&jvm, (void **)&env, &vm_args);
    if (ret != JNI_OK) {
        fprintf(stderr, "Failed to create JVM\\n");
        exit(EXIT_FAILURE);
    }

    // 查找 HelloWorld 类
    jclass cls = (*env)->FindClass(env, "HelloWorld");
    if (cls == NULL) {
        fprintf(stderr, "Failed to find HelloWorld class\\n");
        (*jvm)->DestroyJavaVM(jvm); // 清理 JVM
        exit(EXIT_FAILURE);
    }

    // 创建 HelloWorld 类的实例
    jobject obj = (*env)->AllocObject(env, cls);
    if (obj == NULL) {
        fprintf(stderr, "Failed to create HelloWorld object\\n");
        (*jvm)->DestroyJavaVM(jvm); // 清理 JVM
        exit(EXIT_FAILURE);
    }

    // 查找 sayHello 方法
    jmethodID mid = (*env)->GetMethodID(env, cls, "sayHello", "()V");
    if (mid == NULL) {
        fprintf(stderr, "Failed to find sayHello method\\n");
        (*jvm)->DestroyJavaVM(jvm); // 清理 JVM
        exit(EXIT_FAILURE);
    }

    // 调用 sayHello 方法
    (*env)->CallVoidMethod(env, obj, mid);

    // 销毁 Java 虚拟机
    (*jvm)->DestroyJavaVM(jvm);
    return 0;
}

4. 代码解释

  • 创建 JVM: 使用 JNI_CreateJavaVM 创建 Java 虚拟机实例,并初始化 JNIEnv 指针。传入的参数包括 JVM 选项和 JNI 版本。
  • 查找类: 使用 FindClass 查找 Java 类 HelloWorld。如果找不到类,将打印错误信息并清理 JVM。
  • 创建对象: 使用 AllocObject 创建 HelloWorld 类的实例。如果创建失败,同样会打印错误信息并清理 JVM。
  • 查找方法: 使用 GetMethodID 查找 sayHello 方法的 ID。需要提供方法名和方法签名(()V 表示无参数且无返回值的方法)。
  • 调用方法: 使用 CallVoidMethod 调用 sayHello 方法。
  • 清理: 最后,使用 DestroyJavaVM 销毁 Java 虚拟机,释放资源。

5. 编译和运行

要编译 C/C++ 代码,你需要链接 JNI 库。假设你将代码保存为 HelloWorld.c,可以使用以下命令编译:

gcc -o HelloWorld HelloWorld.c -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -L"$JAVA_HOME/lib" -ljvm

然后运行生成的可执行文件:

./HelloWorld

你应该能看到输出:

Hello from Java!

6. 总结

通过 JavaVMJNIEnv,你可以在 C/C++ 代码中与 Java 进行交互。这个示例展示了如何创建 JVM、查找类和方法以及调用 Java 方法。使用 JNI 时,请确保正确管理资源,避免内存泄漏和其他问题。


GetEnv

是 Java Native Interface (JNI) 中的一个重要函数,用于获取当前线程的 JNIEnv 指针。这个指针提供了调用 JNI 函数的接口,使得本地代码可以与 Java 代码进行交互。以下是对 GetEnv 的详细介绍,包括其用法、参数、返回值以及示例。(主线程获取)

1. 函数原型

jint GetEnv(JavaVM *vm, JNIEnv **env, jint version);

2. 参数说明

  • JavaVM *vm: 指向 Java 虚拟机的指针,通常是在创建 JVM 时获得的。
  • JNIEnv **env: 指向 JNIEnv 指针的指针,用于存储当前线程的 JNIEnv 指针。
  • jint version: 请求的 JNI 版本,通常使用 JNI_VERSION_1_6 或其他版本常量。

3. 返回值

  • 成功时: 返回 JNI_OK,表示成功获取 JNIEnv 指针。
  • 失败时: 返回一个非零的错误代码,例如 JNI_EDETACHED(表示当前线程未附加到 JVM)或 JNI_EVERSION(表示请求的版本不支持)。

4. 使用场景

GetEnv 通常在以下情况下使用:

  • 当线程首次启动时,它可能没有 JNIEnv 指针。在这种情况下,你可以使用 GetEnv 来获取它。
  • 在多线程环境中,确保每个线程都有自己的 JNIEnv 指针。

5. 示例代码

以下是一个示例,展示如何使用 GetEnv 获取 JNIEnv 指针,并调用 Java 方法。

5.1 Java 代码

首先,定义一个简单的 Java 类:

// HelloWorld.java
public class HelloWorld {
    public void sayHello() {
        System.out.println("Hello from Java!");
    }
}

编译这个 Java 文件:

javac HelloWorld.java

5.2 C/C++ 代码

接下来,编写 C/C++ 代码,使用 GetEnv 来获取 JNIEnv 指针并调用 sayHello 方法:

#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

JavaVM *jvm; // 声明全局 Java 虚拟机指针

void* thread_function(void* arg) {
    JNIEnv *env; // 声明 JNI 环境指针
    jint ret;

    // 获取当前线程的 JNIEnv 指针
    ret = (*jvm)->GetEnv(jvm, (void**)&env, JNI_VERSION_1_6);
    if (ret == JNI_EDETACHED) {
        // 如果当前线程未附加到 JVM,则附加它
        JavaVMAttachArgs attachArgs;
        attachArgs.version = JNI_VERSION_1_6;
        attachArgs.name = "Thread-1"; // 线程名称
        attachArgs.group = NULL; // 线程组

        ret = (*jvm)->AttachCurrentThread(jvm, (void**)&env, &attachArgs);
        if (ret != JNI_OK) {
            fprintf(stderr, "Failed to attach thread\\n");
            return NULL;
        }
    } else if (ret != JNI_OK) {
        fprintf(stderr, "Failed to get JNIEnv\\n");
        return NULL;
    }

    // 查找 HelloWorld 类
    jclass cls = (*env)->FindClass(env, "HelloWorld");
    if (cls == NULL) {
        fprintf(stderr, "Failed to find HelloWorld class\\n");
        return NULL;
    }

    // 创建 HelloWorld 类的实例
    jobject obj = (*env)->AllocObject(env, cls);
    if (obj == NULL) {
        fprintf(stderr, "Failed to create HelloWorld object\\n");
        return NULL;
    }

    // 查找 sayHello 方法
    jmethodID mid = (*env)->GetMethodID(env, cls, "sayHello", "()V");
    if (mid == NULL) {
        fprintf(stderr, "Failed to find sayHello method\\n");
        return NULL;
    }

    // 调用 sayHello 方法
    (*env)->CallVoidMethod(env, obj, mid);

    // 如果是附加的线程,分离它
    if (ret == JNI_EDETACHED) {
        (*jvm)->DetachCurrentThread(jvm);
    }

    return NULL;
}

int main() {
    JavaVMInitArgs vm_args; // JVM 启动参数
    JavaVMOption options[1]; // JVM 选项

    // 设置 JVM 选项,指定类路径
    options[0].optionString = "-Djava.class.path=."; // 当前目录
    vm_args.version = JNI_VERSION_1_6; // JNI 版本
    vm_args.nOptions = 1; // 选项数量
    vm_args.options = options; // 选项数组
    vm_args.ignoreUnrecognized = 0; // 忽略未识别的选项

    // 创建 Java 虚拟机
    jint ret = JNI_CreateJavaVM(&jvm, (void**)&vm_args, &vm_args);
    if (ret != JNI_OK) {
        fprintf(stderr, "Failed to create JVM\\n");
        exit(EXIT_FAILURE);
    }

    // 创建线程
    pthread_t thread;
    pthread_create(&thread, NULL, thread_function, NULL);

    // 等待线程结束
    pthread_join(thread, NULL);

    // 销毁 Java 虚拟机
    (*jvm)->DestroyJavaVM(jvm);
    return 0;
}

6. 代码解释

  • 全局 JVM 指针: 在全局范围内声明 JavaVM *jvm,以便在不同的线程中访问。
  • 线程函数: thread_function 中使用 GetEnv 获取 JNIEnv 指针。若当前线程未附加到 JVM,则使用 AttachCurrentThread 附加线程。
  • 查找类和方法: 使用 FindClassGetMethodID 查找 Java 类和方法。
  • 调用方法: 使用 CallVoidMethod 调用 sayHello 方法。
  • 分离线程: 如果线程是附加的,使用 DetachCurrentThread 分离它。

7. 编译和运行

编译 C/C++ 代码时,确保链接 JNI 库。假设代码保存为 HelloWorldThread.c,可以使用以下命令编译:

gcc -o HelloWorldThread HelloWorldThread.c -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -L"$JAVA_HOME/lib" -ljvm -lpthread

运行生成的可执行文件:

./HelloWorldThread

你应该能看到输出:

Hello from Java!

8. 总结

GetEnv 是 JNI 中获取当前线程的 JNIEnv 指针的关键函数。通过合理使用 GetEnv,可以在多线程环境中安全地调用 Java 方法。确保在使用 JNI 时正确管理线程的附加和分离,以避免潜在的资源泄漏和错误。

AttachCurrentThread


在 Java Native Interface (JNI) 中,GetEnvAttachCurrentThread 是两个用于获取和管理 JNI 环境的重要函数。以下是对这两个函数的详细介绍。

1. GetEnv

GetEnv 函数用于获取当前线程的 JNIEnv 指针。通过这个指针,本地代码可以访问 JNI 的功能,例如调用 Java 方法、访问字段等。

函数原型

jint GetEnv(JavaVM *vm, JNIEnv **env, jint version);

参数说明

  • JavaVM *vm: 指向 Java 虚拟机的指针,通常在创建 JVM 时获得。
  • JNIEnv **env: 指向 JNIEnv 指针的指针,用于接收当前线程的 JNI 环境指针。
  • jint version: 期望的 JNI 版本,通常使用 JNI_VERSION_1_6 或其他相应的版本。

返回值

  • 成功时: 返回 JNI_OK
  • 失败时: 返回一个非零的错误代码。

使用示例

JNIEnv *env;
jint result = (*jvm)->GetEnv(jvm, (void**)&env, JNI_VERSION_1_6);
if (result != JNI_OK) {
    // 处理错误
}

2. AttachCurrentThread

AttachCurrentThread 函数用于将当前线程附加到 Java 虚拟机,并获取其 JNIEnv 指针。通常在本地线程(不是由 JVM 创建的线程)需要调用 Java 方法时使用。(子线程获取)

函数原型

jint AttachCurrentThread(JavaVM *vm, JNIEnv **env, void *args);

参数说明

  • JavaVM *vm: 指向 Java 虚拟机的指针。
  • JNIEnv **env: 指向 JNIEnv 指针的指针,用于接收当前线程的 JNI 环境指针。
  • void *args: 可以传递给 Java 线程的参数,通常为 NULL

返回值

  • 成功时: 返回 JNI_OK
  • 失败时: 返回一个非零的错误代码。

使用示例

JNIEnv *env;
jint result = (*jvm)->AttachCurrentThread(jvm, (void**)&env, NULL);
if (result != JNI_OK) {
    // 处理错误
}

3. 结合使用

在一个 JNI 应用程序中,通常会在创建 Java 虚拟机时获得 JavaVM 指针,然后在本地线程中使用 AttachCurrentThread 获取 JNIEnv 指针,最后可以使用 GetEnv 检查当前线程的环境。

示例代码

#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

JavaVM *jvm; // 全局 JavaVM 指针

void* thread_function(void* arg) {
    JNIEnv *env;
    // 将当前线程附加到 JVM
    jint result = (*jvm)->AttachCurrentThread(jvm, (void**)&env, NULL);
    if (result != JNI_OK) {
        fprintf(stderr, "Failed to attach thread\\n");
        return NULL;
    }

    // 现在可以使用 env 调用 Java 方法
    // ...

    // 完成后分离线程
    (*jvm)->DetachCurrentThread(jvm);
    return NULL;
}

int main() {
    JavaVMInitArgs vm_args;
    JavaVMOption options[1];

    options[0].optionString = "-Djava.class.path=."; // 设置类路径
    vm_args.version = JNI_VERSION_1_6; // JNI 版本
    vm_args.nOptions = 1; // 选项数量
    vm_args.options = options; // 选项数组
    vm_args.ignoreUnrecognized = 0; // 忽略未识别的选项

    // 创建 JVM
    jint ret = JNI_CreateJavaVM(&jvm, (void **)&env, &vm_args);
    if (ret != JNI_OK) {
        fprintf(stderr, "Failed to create JVM\\n");
        exit(EXIT_FAILURE);
    }

    // 创建线程
    pthread_t thread;
    pthread_create(&thread, NULL, thread_function, NULL);
    pthread_join(thread, NULL); // 等待线程结束

    // 销毁 JVM
    (*jvm)->DestroyJavaVM(jvm);
    return 0;
}

4. 总结

  • GetEnv 用于获取当前线程的 JNIEnv 指针,适用于已经附加到 JVM 的线程。
  • AttachCurrentThread 用于将本地线程附加到 JVM,并获取 JNIEnv 指针,适用于未由 JVM 创建的线程。

这两个函数在 JNI 编程中非常重要,确保本地线程能够正确地与 Java 代码进行交互。

13.JavaVM的获取方式

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传


(1).在 JNI 中,获取 JavaVM 的方式通常有两种:

通过 JNI 环境 (JNIEnv) 获取和通过 JNI 初始化时的参数获取。以下是详细介绍。

1. 通过 JNI 环境获取 JavaVM

在 JNI 中,每个线程都有一个 JNIEnv 指针,但只有一个 JavaVM 实例。可以通过 JNIEnvGetJavaVM 方法获取 JavaVM 指针。以下是示例代码:

#include <jni.h>
#include <stdio.h>

void someFunction(JNIEnv *env) {
    JavaVM *jvm; // 声明 JavaVM 指针
    // 获取 JavaVM 指针
    if ((*env)->GetJavaVM(env, &jvm) != JNI_OK) {
        fprintf(stderr, "Failed to get JavaVM\\n");
        return;
    }

    // 现在可以使用 jvm 指针进行其他操作
    // ...
}

2. 通过 JNI 初始化时的参数获取

在 JNI 初始化时,通常会通过 JNI_CreateJavaVM 函数创建 JVM,并在该函数的参数中获取 JavaVM 指针。示例代码如下:

#include <jni.h>
#include <stdio.h>

int main() {
    JavaVM *jvm; // 声明 JavaVM 指针
    JNIEnv *env; // 声明 JNI 环境指针
    JavaVMInitArgs vm_args; // JVM 启动参数
    JavaVMOption options[1]; // JVM 选项

    // 设置 JVM 选项
    options[0].optionString = "-Djava.class.path=."; // 设置类路径
    vm_args.version = JNI_VERSION_1_6; // JNI 版本
    vm_args.nOptions = 1; // 选项数量
    vm_args.options = options; // 选项数组
    vm_args.ignoreUnrecognized = 0; // 忽略未识别的选项

    // 创建 Java 虚拟机
    jint ret = JNI_CreateJavaVM(&jvm, (void **)&env, &vm_args);
    if (ret != JNI_OK) {
        fprintf(stderr, "Failed to create JVM\\n");
        return -1;
    }

    // 现在可以使用 jvm 指针进行其他操作
    // ...

    // 销毁 Java 虚拟机
    (*jvm)->DestroyJavaVM(jvm);
    return 0;
}

3. 总结

  • 通过 GetJavaVM: 在已有的 JNIEnv 中获取 JavaVM 指针,适用于已经在 JNI 环境中的线程。
  • 通过 JNI_CreateJavaVM: 在创建 JVM 时获取 JavaVM 指针,适用于程序启动时。

这两种方式都可以有效地获取 JavaVM 指针,以便在本地代码中与 Java 进行交互。

(2)JNI_OnLoad的第一个参数


JNI_OnLoad 是 JNI 中的一个特殊函数,它在共享库被加载时自动调用。这个函数的第一个参数是 JavaVM *vm,用于获取 Java 虚拟机的指针。以下是对 JNI_OnLoad 和其第一个参数的详细介绍。

1. JNI_OnLoad 函数原型

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

2. 参数说明

  • JavaVM *vm:
    • 这是指向 Java 虚拟机的指针。通过这个指针,您可以获取 JNIEnv 指针,并与 Java 进行交互。
    • 该指针在 JNI 环境中是非常重要的,因为它允许本地代码访问 Java 的功能,如创建对象、调用方法等。
  • void *reserved:
    • 这个参数保留供将来使用,通常可以忽略。

3. 使用示例

以下是一个简单的示例,展示如何在 JNI_OnLoad 中使用 JavaVM 指针:

#include <jni.h>
#include <stdio.h>

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env;
    // 获取 JNIEnv 指针
    if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR; // 返回错误
    }

    // 在这里可以进行其他初始化操作
    printf("JNI_OnLoad called, JavaVM pointer obtained.\\n");

    return JNI_VERSION_1_6; // 返回支持的 JNI 版本
}

4. 代码解析

  • 获取 JNIEnv: 使用 GetEnv 函数从 JavaVM 获取 JNIEnv 指针。这个指针可以用于后续的 JNI 调用。
  • 返回值: JNI_OnLoad 返回一个 jint 类型的值,表示支持的 JNI 版本。如果返回 JNI_ERR,表示加载失败。

5. 总结

JNI_OnLoad 的第一个参数 JavaVM *vm 是获取 Java 虚拟机指针的关键。通过这个指针,您可以在本地代码中与 Java 进行交互,执行各种 JNI 操作。确保在 JNI_OnLoad 中正确处理 JNIEnv 的获取和其他初始化工作,以便在后续的 JNI 调用中顺利进行。

(3)JNI_OnUnload的第一个参数


在 JNI 中,JNI_OnLoadJNI_OnUnload 是两个特殊的回调函数,用于在动态链接库(DLL)加载和卸载时执行初始化和清理操作。它们的第一个参数分别是 JavaVM *JNIEnv *。以下是这两个函数的详细介绍。

1. JNI_OnLoad

函数原型

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

参数说明

  • JavaVM *vm: 这是指向 Java 虚拟机的指针。通过这个指针,你可以获取 JNIEnv 指针,并可以在这个虚拟机实例中执行 JNI 操作。
  • void *reserved: 这个参数保留给未来的使用,通常在当前版本中不需要使用。

返回值

  • 返回一个 jint 类型的值,表示 JNI 版本。通常返回 JNI_VERSION_1_6 或其他支持的 JNI 版本。

作用

JNI_OnLoad 在库加载时被调用,通常用于执行一些初始化操作,如注册本地方法、创建全局引用等。

2. JNI_OnUnload

函数原型

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

参数说明

  • JavaVM *vm: 这是指向 Java 虚拟机的指针,允许你执行一些清理操作。
  • void *reserved: 同样,这个参数保留给未来的使用,通常在当前版本中不需要使用。

作用

JNI_OnUnload 在库卸载时被调用,通常用于释放资源,例如删除全局引用、关闭打开的文件等。

3. 示例代码

以下是一个简单的示例,展示如何实现 JNI_OnLoadJNI_OnUnload

#include <jni.h>
#include <stdio.h>

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    printf("JNI_OnLoad called\\n");
    // 可以在这里进行初始化操作
    return JNI_VERSION_1_6; // 返回支持的 JNI 版本
}

JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) {
    printf("JNI_OnUnload called\\n");
    // 在这里进行清理操作
}

4. 使用注意事项

  • 线程安全: 如果在 JNI_OnLoad 中创建了全局引用,确保在 JNI_OnUnload 中释放它们,以避免内存泄漏。
  • 版本兼容性: 在 JNI_OnLoad 中返回的版本号应该是你所支持的 JNI 版本,以确保与 Java 虚拟机的兼容性。
  • 全局状态管理: 如果需要在 JNI_OnLoad 中创建全局变量或状态,确保在 JNI_OnUnload 中进行相应的清理。

5. 总结

JNI_OnLoadJNI_OnUnload 提供了在 JNI 库加载和卸载时执行初始化和清理操作的机制。通过这两个函数,可以管理 JNI 资源,确保应用的稳定性和性能。


(4)指针获取的是否一致

在 JNI 中,获取 JavaVM 指针的方式有几种,主要包括通过 JNI 函数、通过 JNI_OnLoadJNI_OnUnload 函数的参数等。下面我们详细介绍这些方式,并对比它们获取的 JavaVM 指针是否一致。

1. 获取 JavaVM 指针的方式

1.1 JNI_CreateJavaVM

使用 JNI_CreateJavaVM 函数创建 Java 虚拟机时,可以直接获取 JavaVM 指针。

JavaVM *jvm;
JNIEnv *env;
JavaVMInitArgs vm_args;
// 初始化 vm_args 和选项
JNI_CreateJavaVM(&jvm, (void **)&env, &vm_args);

1.2 JNI_GetCreatedJavaVMs

如果已经存在一个 Java 虚拟机,可以使用 JNI_GetCreatedJavaVMs 函数获取已经创建的 JavaVM 指针。

JavaVM *jvm;
jint numVMs;
JNI_GetCreatedJavaVMs(&jvm, 1, &numVMs);

1.3 在 JNI_OnLoad

当动态链接库被加载时,JNI_OnLoad 函数会被调用,传入的第一个参数是 JavaVM 指针。

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
    // 可以使用 jvm
    return JNI_VERSION_1_6;
}

1.4 在 JNI_OnUnload

同样,JNI_OnUnload 函数的第一个参数也是 JavaVM 指针,表示卸载时的 Java 虚拟机实例。

JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *jvm, void *reserved) {
    // 可以使用 jvm
}

2. 获取的 JavaVM 指针是否一致

在不同的上下文中获取的 JavaVM 指针通常是一致的。以下是一些关键点:

  • JNI_CreateJavaVMJNI_GetCreatedJavaVMs: 这两种方式获取的 JavaVM 指针应该是相同的,前提是你是在同一个进程中。如果你在进程中创建了 JVM,后续调用 JNI_GetCreatedJavaVMs 将返回相同的 JavaVM 指针。
  • JNI_OnLoadJNI_OnUnload: 在这两个函数中接收到的 JavaVM 指针也是相同的,且与通过 JNI_CreateJavaVMJNI_GetCreatedJavaVMs 获取的指针一致。

3. 示例验证

以下是一个示例,验证不同方式获取的 JavaVM 指针是否一致:

#include <jni.h>
#include <stdio.h>

JavaVM *g_jvm; // 全局 JavaVM 指针

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
    g_jvm = jvm; // 保存 JavaVM 指针
    printf("JNI_OnLoad: %p\\n", (void*)jvm);
    return JNI_VERSION_1_6;
}

JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *jvm, void *reserved) {
    printf("JNI_OnUnload: %p\\n", (void*)jvm);
}

void check_jvm() {
    JavaVM *jvm;
    jint numVMs;

    // 使用 JNI_GetCreatedJavaVMs 获取 JavaVM 指针
    JNI_GetCreatedJavaVMs(&jvm, 1, &numVMs);
    printf("GetCreatedJavaVMs: %p\\n", (void*)jvm);
    printf("Global JVM: %p\\n", (void*)g_jvm);
}

int main() {
    // 假设在这里创建 JVM
    // 例如使用 JNI_CreateJavaVM

    // 检查 JVM 指针
    check_jvm();

    return 0;
}

4. 总结

在 JNI 中,通过不同方式获取的 JavaVM 指针通常是一致的。无论是通过 JNI_CreateJavaVMJNI_GetCreatedJavaVMs,还是在 JNI_OnLoadJNI_OnUnload 中接收的参数,指向的是同一个 Java 虚拟机实例。这种一致性在多线程和多模块的应用中非常重要,确保了 JNI 的稳定性和可靠性。

14.代码C与C++的区别


在使用 JNI(Java Native Interface)时,无论是用 C 还是 C++ 编写代码,基本的 JNI 操作和 API 调用都是相同的,但在语言特性和代码结构上会有一些显著的区别。以下是 C 和 C++ 在 JNI 编程中的主要区别。

1. 代码结构

C 代码示例

在 C 中,代码通常是以过程为导向的,使用函数进行操作:

#include <jni.h>
#include <stdio.h>

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    // 使用 vm 参数
    return JNI_VERSION_1_6;
}

C++ 代码示例

在 C++ 中,代码可以使用类和对象的概念,结构更为灵活:

#include <jni.h>
#include <iostream>

class MyClass {
public:
    static jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
        // 使用 vm 参数
        return JNI_VERSION_1_6;
    }
};

2. 名称修饰

在 C++ 中,函数的名称会被编译器进行名称修饰(name mangling),这意味着 C++ 的函数名与 C 的函数名不同。因此,JNI 函数的声明和实现需要使用 extern "C" 来避免名称修饰:

extern "C" {
    JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
        return JNI_VERSION_1_6;
    }
}

3. 错误处理

C 和 C++ 在错误处理方面的风格有所不同。C 通常使用返回值来指示错误,而 C++ 可以使用异常处理机制:

C 错误处理示例

if (some_error_condition) {
    return JNI_ERR; // 返回错误码
}

C++ 错误处理示例

try {
    // 可能引发异常的代码
} catch (const std::exception &e) {
    std::cerr << "Error: " << e.what() << std::endl;
    return JNI_ERR;
}

4. 对象和内存管理

C++ 支持对象和类的概念,可以使用构造函数和析构函数来管理资源,而 C 则需要手动管理内存:

C++ 对象管理示例

class MyClass {
public:
    MyClass() {
        // 构造函数
    }
    ~MyClass() {
        // 析构函数
    }
};

C 内存管理示例

typedef struct {
    // 数据成员
} MyClass;

MyClass* createMyClass() {
    MyClass* obj = malloc(sizeof(MyClass));
    // 初始化
    return obj;
}

void destroyMyClass(MyClass* obj) {
    free(obj);
}

5. STL 和标准库

C++ 可以使用 STL(标准模板库)来简化数据结构和算法的使用,例如 std::vectorstd::string 等,而 C 则需要使用原生数组和字符指针:

C++ STL 示例

#include <vector>
#include <string>

std::vector<int> numbers = {1, 2, 3};
std::string message = "Hello, World!";

C 原生数组示例

int numbers[] = {1, 2, 3};
char message[] = "Hello, World!";

6. 总结

尽管 C 和 C++ 在 JNI 编程中可以执行相同的操作,但由于语言特性和编程范式的不同,C++ 提供了更多的灵活性和功能。在选择使用哪种语言时,应考虑项目的需求、团队的熟悉程度以及可维护性等因素。C++ 的面向对象特性使得处理复杂数据结构和逻辑时更加方便,而 C 则更为直接和简单。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值