关于so文件的编写

编译So文件的过程文章:https://blog.csdn.net/carson_ho/article/details/73250163

原理:利用 RegisterNatives 方法来注册 java 方法与 JNI 函数的一一对应关系

这里实现一个计算功能的代码!

实现流程:

1、利用结构体 JNINativeMethod 数组记录 java 方法与 JNI 函数的对应关系

JNINativeMethod的结构体:

typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;

第一个变量name是Java中函数的名字
第二个变量signature是用来字符串是描述了函数的参数和返回值
第三个变量fnPtr是函数指针 指向C函数

第二个signature参数需要讲下,因为我在写这篇文章的时候不懂(这些也会在smali中看到)

举个例(实际上这些字符是与函数的参数类型一一对应的):

"()V" "()" 中的字符表示参数,后面的则代表返回值。例如"()V" 就表示void Func();
"(II)V" 那么"(II)V" 表示 void Func(int, int);
"(Ljava/lang/String;Ljava/lang/String;)V" 那么就表示 void Func(String, String),前面的 L表示类的实例

具体的对应关系:

字符 Java类型 C类型

V void void
Z jboolean boolean
I jint int
J jlong long
D jdouble double
F jfloat float
B jbyte byte
C jchar char
S jshort short

数组则以"["开始,用两个字符表示

[I jintArray int[]
[F jfloatArray float[]
[B jbyteArray byte[]
[C jcharArray char[]
[S jshortArray short[]
[D jdoubleArray double[]
[J jlongArray long[]
[Z jbooleanArray boolean[]

如果JAVA函数位于一个嵌入类,则用$作为类名间的分隔符
例如 "(Ljava/lang/String;Landroid/os/FileUtils$FileStatus;)Z" 结尾返回值为布尔值

这里注册的有加减乘除4个函数,如下实现:

JNINativeMethod nativeMethod[] = {
//java层的方法名;Java层的签名;指针指向C的函数
{"addTest","(FF)F",(void*)addTest}, // 那么这里的意思 参数两个float类型 返回值为float
{"subTest","(FF)F",(void*)subTest},
{"mulTest","(FF)F",(void*)mulTest},
{"divTest","(FF)F",(void*)divTest}
};

2、实现 JNI_OnLoad 方法,在加载动态库后,执行动态注册

JNIEXPORT jint JNI_OnLoad(JavaVm* vm, void* reserved)

个人理解:可以理解为DllMain入口, 在System.loadLibrary(xxx)函数时被调用

3、调用 FindClass 方法,获取 java 对象

jclass _jclass = (*env)->FindClass(env, "com/example/myjnitest/MainActivity");

4、调用 RegisterNatives 方法,传入 java 对象,以及 JNINativeMethod 数组,以及注册数目完成注册

if((*env)->RegisterNatives(env, _jclass, nativeMethod, sizeof(nativeMethod)/sizeof(nativeMethod[0]))!=JNI_OK){
return JNI_ERR;
}

坑点:jni注册的方法,一定要在java层进行声明,否则会报错,这尼玛巨坑。。。

计算器的实现:

.c文件:

#include <com_example_myjnitest_MainActivity.h>
jfloat addC(JNIEnv* env, jobject obj,jfloat a, jfloat b){
return a+b;
}
jfloat subC(JNIEnv* env,jobject obj,jfloat a,jfloat b){
return a-b;
}
jfloat mulC(JNIEnv* env,jobject obj,jfloat a,jfloat b){
return a*b;
}
jfloat divC(JNIEnv* env,jobject obj,jfloat a,jfloat b){
return a/b;
}
static JNINativeMethod nativeMethod[] = {
//java层的方法名;Java层的签名;指针指向C的函数
{"addTest","(FF)F",(void*)addC},// 那么这里的意思 参数两个float类型 返回值为float
{"subTest","(FF)F",(void*)subC},
{"mulTest","(FF)F",(void*)mulC},
{"divTest","(FF)F",(void*)divC}
};
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){ // 可以直接理解为dll的 dllmain 在System.loadLibrary(xxx)函数时被调用
JNIEnv* env;
//从JavaVM获取JNIEnv,一般使用1.4的版本,若未提供JNI_OnLoad()函数,VM会默认该使用最老的JNI 1.1版
if((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_4)!=JNI_OK){
return JNI_ERR;
}
//在MainActivity中进行注册Native数组方法
jclass _jclass = (*env)->FindClass(env, "com/example/myjnitest/MainActivity");
if((*env)->RegisterNatives(env, _jclass, nativeMethod, sizeof(nativeMethod)/sizeof(nativeMethod[0]))!=JNI_OK){
return JNI_ERR;
}
return JNI_VERSION_1_4; //必须返回该值
}

头文件

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_myjnitest_MainActivity */
#ifndef _Included_com_example_myjnitest_MainActivity
#define _Included_com_example_myjnitest_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_myjnitest_MainActivity
* Method: getFirstJniText
* Signature: ()Ljava/lang/String;
*/
jfloat addTest(JNIEnv* env,jobject obj,jfloat a,jfloat b);
jfloat subTest(JNIEnv* env,jobject obj,jfloat a,jfloat b);
jfloat mulTest(JNIEnv* env,jobject obj,jfloat a,jfloat b);
jfloat divTest(JNIEnv* env,jobject obj,jfloat a,jfloat b);
#ifdef __cplusplus
}
#endif
#endif

MainActivity:

package com.example.myjnitest;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends Activity {
static{
System.loadLibrary("com_example_myjnitest_MainActivity");
}
public native float addTest(float a, float b); // 声明native方法
public native float subTest(float a, float b); // 声明native方法
public native float mulTest(float a, float b); // 声明native方法
public native float divTest(float a, float b); // 声明native方法
Button btn01;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn01 = (Button) findViewById(R.id.button1);
btn01.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
float a = addTest(Float.parseFloat("1.1".toString()), Float.parseFloat("2.2".toString()));
Toast.makeText(getApplicationContext(), "" + a, Toast.LENGTH_SHORT).show();
}
});
}
}

posted @   zpchcbd  阅读(732)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
点击右上角即可分享
微信分享提示