关于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 @ 2020-09-20 03:09  zpchcbd  阅读(751)  评论(0)    收藏  举报