找回密码
 立即注册
首页 业界区 安全 【JNI】JNI环境搭建

【JNI】JNI环境搭建

万妙音 5 天前
1 前言

​    JNI (Java Native Interface) 是 JDK 提供的一种机制,用于实现 Java 代码与其他语言(主要是 C 和 C++)编写的本地代码之间的交互。
​    JNI 接口详见 JDK 安装目录中的 include/jni.h 文件,Android NDK 对 JDK 的 JNI 进行了扩展,对应的 jni.h 文件路径如下。
  1. SDK\ndk\27.2.12479018\toolchains\llvm\prebuilt\windows-x86_64\sysroot\usr\include\jni.h
复制代码
​     JNI 官方文档见 → https://docs.oracle.com/en/java/javase/17/docs/specs/jni/index.html。
1.png

2 在命令行中编译代码

2.1 环境准备

​    1)安装 JDK
​    下载 JDK(建议下载 x64 Compressed Archive 文件),解压后将根目录写入 JAVA_HOME 环境变量中,如下。
2.png

​    然后将 %JAVA_HOME%、%JAVA_HOME%\bin 加入 Path 环境变量中,如下。
3.png

​    在命令行分别输入 java --version、 javac --version,如果有打印版本号,说明 JDK 环境搭建成功。
​    2)安装 MinGW
​    MinGW(Minimalist GNU for Windows)是一个用于 Windows 平台的轻量级 GNU 开发环境,它允许开发者在 Windows 上使用 GCC(GNU Compiler Collection)工具链编译本地 Windows 应用程序。
​    下载 MinGW,解压后将根目录下的 bin 目录写入 Path 环境变量中。
4.png

​    在命令行输入 gcc --version、g++ --version, 如果有打印版本号,说明 MinGW 环境搭建成功。
2.2 一个简单的案例

​    1)编写 java 代码
​    用记事本编辑以下代码,保存为 HelloJNI.java。
​    HelloJNI.java
  1. /**
  2. * 1. 生成字节码和头文件
  3. *    javac -encoding UTF-8 -h ./ HelloJNI.java (或 javah -encoding UTF-8 ./ HelloJNI.java, jdk10 之前)
  4. *
  5. * 2. 生成 dll 或 so
  6. *    g++ -fPIC -I"%JAVA_HOME%\\include" -I"%JAVA_HOME%\\include\\win32" -shared -o hello.dll HelloJNI.c
  7. *
  8. * 3. 运行
  9. *    java HelloJNI
  10. */
  11. public class HelloJNI {
  12.    static {
  13.       // 运行时加载 native 库, hello.dll (Windows) 或 hello.so (Linux/Unix/Android)
  14.       System.loadLibrary("hello");
  15.    }
  16.    private native void sayHello();
  17.    public static void main(String[] args) {
  18.       new HelloJNI().sayHello();
  19.    }
  20. }
复制代码
​    2)生成字节码和头文件
​    在命令行执行以下命令,生成字节码(.class 文件)和头文件(.h 文件)。
  1. // jdk10 及之后
  2. javac -encoding UTF-8 -h ./ HelloJNI.java
  3. // jdk10 之前
  4. javah -encoding UTF-8 ./ HelloJNI.java
复制代码
​    执行完以上命令后,会生成 HelloJNI.class 和 HelloJNI.h 文件。HelloJNI.h 文件内容如下。
  1. /* DO NOT EDIT THIS FILE - it is machine generated */
  2. #include <jni.h>
  3. /* Header for class HelloJNI */
  4. #ifndef _Included_HelloJNI
  5. #define _Included_HelloJNI
  6. #ifdef __cplusplus
  7. extern "C" {
  8. #endif
  9. /*
  10. * Class:     HelloJNI
  11. * Method:    sayHello
  12. * Signature: ()V
  13. */
  14. JNIEXPORT void JNICALL Java_HelloJNI_sayHello
  15.   (JNIEnv *, jobject);
  16. #ifdef __cplusplus
  17. }
  18. #endif
  19. #endif
复制代码
​    3)编写 C 代码
  1. #include <jni.h>
  2. #include <stdio.h>
  3. #include "HelloJNI.h"
  4. JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject thisObj) {
  5.    printf("Hello World!\n");
  6.    return;
  7. }
复制代码
​    4)生成 dll 或 so
​    在命令行执行以下命令,生成 ddl(或 so)文件。
  1. // 生成 dll 文件 (Windows)
  2. g++ -fPIC -I"%JAVA_HOME%\\include" -I"%JAVA_HOME%\\include\\win32" -shared -o hello.dll HelloJNI.c
  3. // 生成 so 文件 (Linux/Unix/Android)
  4. g++ -fPIC -I"%JAVA_HOME%\\include" -I"%JAVA_HOME%\\include\\win32" -shared -o hello.so HelloJNI.c
复制代码
​    5)运行代码
​    在命令行执行以下命令,运行代码。
  1. java HelloJNI
复制代码
​    运行结果如下。
  1. Hello World!
复制代码
​    6)一键编译运行脚本
  1. @echo off
  2. set class_name=HelloJNI
  3. set native_file_name=HelloJNI
  4. set lib_name=hello
  5. :: 删除缓存文件
  6. del %class_name%.class
  7. del %class_name%.h
  8. del %lib_name%.dll
  9. :: 生成字节码和头文件
  10. javac -encoding UTF-8 -h ./ %class_name%.java
  11. :: 生成 dll 或 so
  12. g++ -fPIC -I"%JAVA_HOME%\\include" -I"%JAVA_HOME%\\include\\win32" -shared -o %lib_name%.dll %native_file_name%.c
  13. :: 运行
  14. java %class_name%
  15. pause
复制代码
3 在 Android Studio 中编译代码

​    在 Android Studio 中,依此选择 New -> New Project -> Native C++,创建 Native C++ 项目。
5.png

​    也可以通过 New -> New Module -> Android Native Library,创建 Native Library。
6.png

​    对于普通的 Android 项目,可以通过以下配置,使其支持 JNI。
​    1)build.gradle
  1. defaultConfig {
  2.     ndk {
  3.         abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
  4.     }
  5. }
  6. externalNativeBuild {
  7.     cmake {
  8.         path file('src/main/cpp/CMakeLists.txt')
  9.         version '3.22.1'
  10.     }
  11. }
复制代码
​    abiFilters 的作用是指定应用支持的 ABI(Application Binary Interface) 架构类型,即应用只会为指定的 CPU 架构生成原生库(.so 文件),而忽略其他架构。如果未配置 abiFilters,Gradle 默认会构建所有支持的 ABI(取决于 NDK 版本和项目依赖)。
  1. armeabi-v7a: 32 位 ARM 架构 (较旧的 Android 设备)
  2. arm64-v8a: 64 位 ARM 架构 (现代主流设备,性能更好)
  3. x86: 32 位 Intel 架构 (模拟器或少数平板设备)
  4. x86_64: 64 位 Intel 架构 (模拟器或高端设备)
复制代码
​    2)创建 cpp 目录
7.png

​    3)C/C++ 代码
​    如果用 C 语言实现,代码如下。
​    demo.c
  1. #include <jni.h>
  2. #include <string.h>
  3. JNIEXPORT jstring JNICALL
  4. Java_com_zhyan8_test_MainActivity_stringFromJNI(
  5.         JNIEnv* env,
  6.         jobject /* this */) {
  7.     return (*env)->NewStringUTF(env, "Hello from C");
  8. }
复制代码
​    如果用 C++ 语言实现,代码如下。
​    demo.cpp
  1. #include <jni.h>
  2. #include <string>
  3. extern "C" JNIEXPORT jstring JNICALL
  4. Java_com_zhyan8_test_MainActivity_stringFromJNI(
  5.         JNIEnv* env,
  6.         jobject /* this */) {
  7.     return env->NewStringUTF("Hello from C++");
  8. }
复制代码
​    可以看到,C 与 C++ 的主要区别如下。

  • 头文件:C 中 string 的引用是 #include ,C++ 中 string 的引用是 #include 。
  • extern "C":C++ 中多了 extern "C" 修饰,表示使用 C 风格的函数链接,禁止 C++ 的名称修饰。如果不禁用名称修饰,C++ 编译器会对函数名进行修饰,以支持函数重载等特性,会导致 Java 无法查找到本地函数。因此,使用 extern "C" 的函数不能重载(因为名称不再修饰)。
  • env:C 中的 env 是二级指针,所以访问函数通过 (*env)-> 访问;C++ 中的 env 是一级指针,所以访问函数通过 env-> 访问。
​    4)CMakeLists.txt
  1. # 设置最小CMake版本
  2. cmake_minimum_required(VERSION 3.22.1)
  3. # 声明项目名, 可以通过${PROJECT_NAME}访问, 在顶层CMakeLists.txt中, 也可以通过${CMAKE_PROJECT_NAME}访问
  4. project("hello")
  5. # 设置源码包路径 (相对于当前CMakeLists.txt的路径)
  6. set(PACKAGES_DIR "com/zhyan8/demo")
  7. # 头文件目录列表 (便于以该路径为相对路径访问头文件)
  8. include_directories(${PACKAGES_DIR})
  9. # 源文件列表 (相对于当前CMakeLists.txt的路径)
  10. file(GLOB_RECURSE SOURCES ./${PACKAGES_DIR} *.cpp)
  11. # 添加预构建库, SHARED用于将该库声明为一个shared library
  12. # 在Java/Kotlin中, 需要通过System.loadLibrary()加载该库
  13. add_library(${CMAKE_PROJECT_NAME} SHARED ${SOURCES})
  14. # 添加链接的三方库文件
  15. target_link_libraries(${CMAKE_PROJECT_NAME}
  16.         # 链接到目标库的库文件
  17.         android
  18.         log)
复制代码
​    5)MainActivity.java
  1. package com.zhyan8.test;
  2. import androidx.appcompat.app.AppCompatActivity;
  3. import android.os.Bundle;
  4. import android.util.Log;
  5. public class MainActivity extends AppCompatActivity {
  6.     static {
  7.         System.loadLibrary("hello");
  8.     }
  9.     @Override
  10.     protected void onCreate(Bundle savedInstanceState) {
  11.         super.onCreate(savedInstanceState);
  12.         setContentView(R.layout.activity_main);
  13.         Log.i("MainActivity", stringFromJNI());
  14.     }
  15.     public native String stringFromJNI();
  16. }
复制代码
​    6)打印日志
​    如果想在 JNI 中使用 Android 中的日志类打印日志,可以使用以下代码。
  1. #include <jni.h>
  2. #include
  3. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "JNI_TAG", __VA_ARGS__)
  4. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "JNI_TAG", __VA_ARGS__)
  5. #define LOGW(...) __android_log_print(ANDROID_LOG_WARN, "JNI_TAG", __VA_ARGS__)
  6. #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "JNI_TAG", __VA_ARGS__)
  7. extern "C"
  8. JNIEXPORT void JNICALL
  9. Java_com_zhyan8_test_MainActivity_testLog(JNIEnv* env, jobject /* this */) {
  10.     LOGD("value1=%d", true);
  11.     LOGI("value2=%d", 100);
  12.     LOGW("value3=%f", 3.5f);
  13.     LOGE("value4=%s", "abcd");
  14. }
复制代码
​    声明:本文转自【JNI】JNI环境搭建。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册