关于jni:C实现Java-native方法

利用场景通过JNI, 实现Java调用C++代码 环境JDK: jdk-17.0.9mingw-w64: x86_64-8.1.0-release-win32-seh-rt_v6-rev0操作系统: windows11 实现步骤1. 申明native办法NativeCommand.java /** * # 生成头文件 * javac -encoding UTF-8 -h . NativeCommand.java * -jni示意生成c++头文件, 从Java 10开始,-jni 选项曾经不再须要了,因为当初 javac -h 默认就会生成C++的头文件。 * */public class NativeCommand { /** * 发送命令给Cpp * @param command 命令 * @param args 参数 * @return 响应 */ public native String send(String command, String... args);}下面定义了办法public native send(xxx) 2. 生成.h头文件执行命令即可(如果是JDK8, 须要应用javah命令)javac -encoding UTF-8 -h . NativeCommand.java jdk17曾经将javah合并到javac中了, 应用javac -h代替javah命令执行实现后当前目录会生成NativeCommand.h文件 3. 编写C++代码NativeCommand.cpp ...

February 21, 2024 · 1 min · jiezi

Android NDK开发之JNI基础

前言之前写了一篇文章简单的介绍了Android NDK的组件和结构,以及在Android studio中开发NDK,NDK是Android底层的c/c++库,然而要在java中调用c/c++的原生功能,则需要使用JNI来实现。什么是JNIJNI(Java Native Interface)是java本地接口,它主要是为了实现Java调用c、c++等本地代码所封装的一层接口。大家都知道java是跨平台开发语言,它的狂平台特性导致与本地交互的能力不够强大,一些和操作系统相关的特性Java无法完成,所以Java提供了JNI用于和Native代码进行交互。通过JNI,Java可以调用c、c++,相反,c、c++也可以调用Java的相关代码。创建NDK工程开发环境MacAndroid studio:3.3.2新建工程本地的Android studio版本为3.3.2,当你创建项目的时候有一个选项是选择Native C++的模板点击next,配置项目的信息点击next,选择使用哪种C++标准,选择Toolchain Default会使用默认的CMake设置即可。点击finish即可完成工程的创建。工程结构这时候主工程目录下会有cpp文件夹和.externalNativeBuild文件夹。.externalNativeBuild文件夹:用于存放cmake编译好的文件,包括支持的各种硬件等信息,有点类似于build.gradle文件明确Gradle如何编译APP;cpp文件夹:存放C/C++代码文件,native-lib.cpp文件默认生成的;cpp文件夹下有两个文件,一个是native-lib.cpp文件,一个是CMakeLists.txt文件。CMakeLists.txt文件是cmake脚本配置文件,cmake会根据该脚本文件中的指令去编译相关的C/C++源文件,并将编译后产物生成共享库或静态块,然后Gradle将其打包到APK中。CMakeLists.txt的相关配置如下:# 设置构建本地库所需的最小版本的cbuild。cmake_minimum_required(VERSION 3.4.1)# 创建并命名一个库,将其设置为静态# 或者共享,并提供其源代码的相对路径。# 您可以定义多个库,而cbuild为您构建它们。# Gradle自动将共享库与你的APK打包。add_library( native-lib #设置库的名称。即SO文件的名称,生产的so文件为“libnative-lib.so”, 在加载的时候“System.loadLibrary(“native-lib”);” SHARED # 将库设置为共享库。 native-lib.cpp # 提供一个源文件的相对路径 helloJni.cpp # 提供同一个SO文件中的另一个源文件的相对路径 )# 搜索指定的预构建库,并将该路径存储为一个变量。因为cbuild默认包含了搜索路径中的系统库,所以您只需要指定您想要添加的公共NDK库的名称。cbuild在完成构建之前验证这个库是否存在。find_library(log-lib # 设置path变量的名称。 log # 指定NDK库的名称 你想让CMake来定位。 )#指定库的库应该链接到你的目标库。您可以链接多个库,比如在这个构建脚本中定义的库、预构建的第三方库或系统库。target_link_libraries( native-lib # 指定目标库中。与 add_library的库名称一定要相同 ${log-lib} # 将目标库链接到日志库包含在NDK。 )#如果需要生产多个SO文件的话,写法如下add_library( natave-lib # 设置库的名称。另一个so文件的名称 SHARED # 将库设置为共享库。 nataveJni.cpp # 提供一个源文件的相对路径 )target_link_libraries( natave-lib #指定目标库中。与 add_library的库名称一定要相同 ${log-lib} # 将目标库链接到日志库包含在NDK。 ) build.gradle中有CMake的相关配置代码结构java调用c、c++代码分为三个步骤:加载so库编写java函数编写c函数在MainActivity.java,static{}语句中使用了加载so库,此语句在类加载中只执行一次。static { System.loadLibrary(“native-lib”);}然后,编写了原生的函数,函数名中要带有native。public native String stringFromJNI();最后,编写相对应的c函数,注意函数名的构成Java_com_example_myapplication_MainActivity_stringFromJNI为Java_加上包名、类型、方法名的下划线连成一起。native-lib.cpp文件#include <jni.h>#include <string>extern “C” JNIEXPORT jstring JNICALLJava_com_example_myapplication_MainActivity_stringFromJNI( JNIEnv env, jobject / this */) { std::string hello = “Hello from C++”; return env->NewStringUTF(hello.c_str());}这就是一个JNI方法调用示例。虽然Java函数不带参数,但是原生方法却带了两个参数,第一个参数JNIEnv是指向可用JNI函数表的接口指针,第二个参数jobject是Java函数所在类的实例的Java对象引用。JNIEnv接口指针原生代码(c)通过JNIEnv接口指针提供的各种函数来使用虚拟机的功能,JNIEnv是一个指向线程-局部数据的指针,线程-局部数据中包含指向函数表的指针。原生代码是c与原生代码是c++的调用JNI函数的语法不同,在c代码中,JNIEnv是指向JNINativeInterface结构的指针,而在c++代码中,JNIEnv是c++类实例,这两种方式调用函数的方式是不一样的。例如:c代码中:(*env)->NewStringUTF(env,“Hello from JNI”);c++代码中:env->NewStringUTF(“Hello from JNI”);实例方法与静态方法Java程序设计有两类方法,实例方法和静态方法。实例方法与类实例相关,只能在类实例中调用。静态方法不与类死里相关,它们可以在静态上下文中直接调用。在原生代码中可以获取Java类的实例引用和类引用。例如:类实例引用extern “C” JNIEXPORT void JNICALLJava_com_example_myapplication_MainActivity_stringFromJNI( JNIEnv *env, jobject thiz) {}类引用extern “C” JNIEXPORT void JNICALLJava_com_example_myapplication_MainActivity_stringFromJNI( JNIEnv *env, jclass clazz) {}从函数中看出来,JNI提供了自己的数据类型从而让原生代码了解Java数据类型。JNI数据类型JNI的数据类型包含两种:基本类型和引用类型。与Java数据类型的对应关系如下:基本数据类型:| JNI类型 | Java类型 || —— | —— | | jboolean | boolean || jbyte | byte | | jchar | char || jshort | short || jint | int || jlong | long || jfloat | float || jdouble | double || void | void |引用类型:| JNI类型 | Java类型 || —— | —— | | jobject | Object || jclass | Class | | jstring | String || jobjectArray | Object[] || jbooleanArray | boolean[] || jbyteArray | char[] || jshortArray | short[] || jintArray | int[] || jlongArray | long[] | | jfloatArray | float[] | | jdoubleArray | double[] | | jthrowable | Throwable |引用数据类型的操作JNI提供了与引用类型密切相关的一组API,这些API通过JNIEnv接口指针提供给原生函数。例如:字符串数组NIO缓冲区字段方法字符串操作JNI把Java字符串当成引用类型处理,提供了Java与c字符串之间相互转换的必要函数,由于Java字符串对象是不可变得,所以JNI不提供修改现有Java字符串内容的函数。创建字符串可以在原生代码中使用NewString函数构建Unicode编码格式的字符串实例,也可以中NewStringUTF函数构建UTF-8编码格式的字符串实例,这些函数以C字符串为参数,并返回一个Java字符串引用类型jstring值。例如:jstring javaStr = (env)->NewStringUTF(env,“Hello”);把Java字符串转换成C字符串为了在原生代码中使用Java字符串,需要将Java字符串转换成C字符串。用GetStringChars函数可以将Unicode格式的Java字符串转换成C字符串,用GetStringUTFChars函数可以将UTF-8格式的Java字符串转换成C字符串。例如:const jbyte strjboolean isCopy;str = (*env)->GetStringUTFChars(env,javaString,&isCopy);释放字符串通过JNI GetStringChars函数和GetStringUTFChars函数获得的C字符串在原生代码中使用完后要释放,否则会引起内存泄漏。JNI提供了ReleaseStringChars函数和ReleaseStringUTFChars函数来释放Unicode编码和UTF-8编码格式的字符串。例如:(*env)->ReleaseStringUTFChars(env,javaString,str);数组操作创建数组用New"Type"Array函数在原生代码中创建数组实例,其中"Type"可以是Int、Char等类型,例如: jintArray javaArray = (env)->NewIntArray(env,10);访问数组元素将数组的代码复制成C数组或者让JNI提供直接指向数组元素的指针方式来访问Java数组元素。对副本的操作Get"Type"ArrayRegion函数将给定的基本Java数组复制到给定的C数组中,例如: jint nativeArray[10]; (env)->GetIntArrayRegion(env,javaArray,0,10,nativeArray);原生代码可以使用和修改数组元素,使用Set"Type"ArrayRegion函数将C数组复制回Java数组中。例如:(env)->SetIntArrayRegion(env,javaArray,0,10,nativeArray);NIO操作JNI提供了在原生代码中使用NIO的函数,与数组操作相比,NIO性能较好,更适合在原生代码和Java应用程序之间传送大量数据。创建直接字节缓冲区unsigned char buffer = (unsigned char) malloc(1024);jobject directBuffer = (env)->NewDirectByteBuffer(env,buffer,1024);注意:原生函数应用通过释放未使用的内存分配以避免内存泄漏。获取直接字节缓冲区unsigned char buffer;buffer = (unsigned char)(*env)->GetDirectBufferAddress(env,directBuffer);访问域Java有两类域:实例域和静态域,这两个的区别就是有没有static声明静态。获取域IDJNI提供了用域ID访问两类域的方法,可以通过给定实例的class对象获取域ID,用GetObjectClass函数来获取class对象。例如:jclass clazz = (*env)->GetObjectClass(env,instance);用GetFieldId函数来获取实例域。jfieldId instanceFieldId = (*env)->GetFieldId(env,clazz,“instanceField”,“Ljava/lang/String”);用GetStaticFieldId获取静态域ID。jfieldID staticFieldId = (*env)->GetStaticFieldID(env,clazz,“staticField”,“Ljava/lang/String”);其中最后一个参数是Java中表示域类型的域描述符,“Ljava/lang/String"表明域类型是String。获取域获得域ID之后可以用Get"Type"Field函数获取实际的实例域。例如:jstring instanceField = (*env)->GetObjectField(env,instance,instanceFieldId);用GetStatic"Type"Field函数获得静态域。例如:jstring staticField = (*env)->GetStaticObjectField(env,clazz,staticFieldId);调用方法与域类似,Java中有两类方法:实例方法和静态方法。获取方法IDJNI提供了用方法ID访问两类方法的途径,可以用给定实例的class对象获取方法ID,用GetMethodID函数获得实例方法的方法ID。例如:jmethodID instanceMethodId = (*env)->GetMethodID(env,clazz,“instanceMethod”,”()Ljava/lang/String;");用GetStaticMethodID函数获得静态域的方法ID,例如:jmethodID staticMethodId=(*env)->GetStaticMethodID(env,clazz,“staticMethod”,"()Ljava/lang/String;");调用方法以方法ID为参数通过Call"Type"Method类函数调用实际的实例方法。例如:jstring instanceMethodResult = (*env)->CallStringMetthod(env,instance,instanceMethodId);用CallStatic"Type"Field类函数调用静态方法,例如:jstring staticMethodResult = (*env)->CallStaticStringMethod(env,clazz,staticMethodId);域和方法描述符在上面获取域ID和方法ID均分别需要域描述符和方法描述符,域描述符和方法描述符可以通过下表Java类型签名映射获取:| Java类型 | 签名|| —— | —— | | Boolean | Z || Byte | B | | Char | C || Short | S || Long | J || Int | I || Float | F || Double | D || void | V || fully-qualified-class | Lfully-qualified-class | | type[] | [type | | method type | (arg-type)ret-type | 类的签名采用"L+包名+类名+;“的形式,将其中的.替换为/即可,比如java.lang.String,它的签名为Ljava/lang/String;数组的签名就是[+类型签名,比如int数组,签名就是[I,多维数组就是[[I。方法的签名为(参数类型签名)+返回值类型签名,例如:boolean fun1(int a,double b,int[] c),其中参数类型的签名为ID[I,返回值类型的签名为Z,所以这个方法的签名就是(ID[I)Z。 ...

March 17, 2019 · 2 min · jiezi

Android进阶: 10分钟实现NDK-JNI 开发教程

项目简介JNI:Java Native Interface(Java 本地编程接口),一套编程规范,它提供了若干的 API 实现了 Java 和其他语言的通信(主要是 C/C++)。Java 可以通过 JNI 调用本地的 C/C++ 代码,本地的 C/C++ 代码也可以调用 java 代码。Java 通过 C/C++ 使用本地的代码的一个关键性原因在于 C/C++ 代码的高效性。NDK:Native Development Kit(本地开发工具),一系列工具的集合,提供了一系列的工具,帮助开发者快速开发 C/C++,极大地减轻了开发人员的打包工作。项目环境Android studio 3.1.2gradle 4.4 plugin 3.1.2targetSdkVersion 28Jni三部曲1.新建Java文件编写相关代码2.通过命令工具Terminal生成.h文件3.新建.c 或者.cpp文件编写相关代码环境配置1.安装NDK+CMakeNDK:这套工具集允许为 Android 使用 C 和 C++ 代码。CMake:一款外部构建工具,可与 Gradle 搭配使用来构建原生库。如果只计划使用 ndk-build,则不需要此组件。Ps:CMake 是 AS 2.2 之后加入的一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程),简单来说就是简化 JNI 开发的编译步骤2.NDK环境配置1.local.propertiesndk.dir=D:\workTime\android-studio-sdk-2.3\android-studio-sdk-2.3\ndk-bundlesdk.dir=D:\workTime\android-studio-sdk-2.3\android-studio-sdk-2.32.gradle.properties#gradle:3.0.1 studio3.0 之前用android.useDeprecatedNdk=true#gradle:3.0.1 studio3.0 之后用android.deprecatedNdkCompileLease=15118326988133.build.gradle中添加CMakeandroid { ……… externalNativeBuild { cmake { path “CMakeLists.txt” } }}4.在app目录下新建CMakeLists.txtCMakeLists.txt所在目录和上面path “CMakeLists.txt"相关连CMakeLists.txt中内容如下:# CMake的编译脚本配置文件# 1. 标注需要支持的CMake最小版本cmake_minimum_required(VERSION 3.4.1)# 2. add_library 定义需要编译的代码库 名称, 类型, 包含的源码add_library( # Sets the name of the library. JNIControl # Sets the library as a shared library. SHARED src/main/jni/JNIControl.cpp)# 3. find_library 定义当前代码库需要依赖的系统或者第三方库文件(可以写多个)find_library( log_lib # 指定要查找的系统库, 给一个名字 log # 真正要查找的liblog.so或者liblog.a)# 4. target_link_libraries设置最终编译的目标代码库target_link_libraries( JNIControl # add_library 生成的 ${log_lib} # find_library 找到的系统库)}到这里环境就搭建完成了,那么下面我们开始装逼了。。。执行装逼三部曲1.新建要编译成.h文件的java文件/** * <pre> * author : Wp * e-mail : 18141924293@163.com * time : 2018/11/15 * desc : * version: 1.0 * </pre> /public class JNIUtils { static { //JNIControl 后面新建的.c 或者.cpp 文件名 在这里可以先注释掉 System.loadLibrary(“JNIControl”); } public static native String printStringByJni();}2.打开Android studio 最下面的命令工具Terminal1.进入java目录下,默认为项目根目录cd app/src/main/java2.如上图,确保在java目录下,执行以下命令,会在java目录下生成.h文件javah king.bird.ndkjnidemo.JNIUtils3.main下面新建jni文件夹,将.h文件拷贝过来4. .h文件如下/ DO NOT EDIT THIS FILE - it is machine generated /#include <jni.h>/ Header for class king_bird_ndkjnidemo_JNIUtils /#ifndef _Included_king_bird_ndkjnidemo_JNIUtils#define _Included_king_bird_ndkjnidemo_JNIUtils#ifdef __cplusplusextern “C” {#endif/ * Class: king_bird_ndkjnidemo_JNIUtils * Method: printStringByJni * Signature: ()Ljava/lang/String; */JNIEXPORT jstring JNICALL Java_king_bird_ndkjnidemo_JNIUtils_printStringByJni (JNIEnv *, jclass);#ifdef __cplusplus}#endif#endif3.编写.c或者.cpp文件JNIControl.cpp文件内容:#include “king_bird_ndkjnidemo_JNIUtils.h”//king_bird_ndkjnidemo_JNIUtils_printStringByJni 包名+文件名+文件内方法名JNIEXPORT jstring JNICALL Java_king_bird_ndkjnidemo_JNIUtils_printStringByJni (JNIEnv *env, jclass jclass){ //字符串返回return env->NewStringUTF(“没想到吧!我竟然会JNI了!!!”);}到这里已经大功告成了1.MainActivity文件package king.bird.ndkjnidemoimport android.support.v7.app.AppCompatActivityimport android.os.Bundleimport kotlinx.android.synthetic.main.activity_main.*class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) mBtnLoadNative.setOnClickListener { val jniUtils = JNIUtils.printStringByJni() mTvText.text = jniUtils } }}2.activity_main.xml文件<?xml version=“1.0” encoding=“utf-8”?><android.support.constraint.ConstraintLayout xmlns:android=“http://schemas.android.com/apk/res/android" xmlns:app=“http://schemas.android.com/apk/res-auto" xmlns:tools=“http://schemas.android.com/tools" android:layout_width=“match_parent” android:layout_height=“match_parent” tools:context=".MainActivity”> <TextView android:id=”@+id/mTvText” android:layout_width=“wrap_content” android:layout_height=“wrap_content” android:text=“Hello World!” app:layout_constraintBottom_toBottomOf=“parent” app:layout_constraintLeft_toLeftOf=“parent” app:layout_constraintRight_toRightOf=“parent” app:layout_constraintTop_toTopOf=“parent” /> <Button android:id=”@+id/mBtnLoadNative" android:layout_width=“match_parent” android:layout_height=“wrap_content” android:text=“LoadNativeText” app:layout_constraintTop_toBottomOf="@+id/mTvText" app:layout_constraintBottom_toBottomOf=“parent” app:layout_constraintStart_toStartOf=“parent” app:layout_constraintEnd_toEndOf=“parent” tools:ignore=“HardcodedText” /></android.support.constraint.ConstraintLayout>参与贡献Fork 本项目新建 Feat_xxx 分支提交代码新建 Pull Request个人说明编译报错或有什么问题call meQQ群:830556582QQ:1101313414github地址你的star和fork是我永生的追求 ...

November 15, 2018 · 2 min · jiezi