第一章:JNI的简单使用(4)-访问c程序

上一小节,介绍了界面设计怎么和具体功能函数联系在一起。下面将介绍在andriod系统中怎么访问C程序
在java中是无法调用C函数的,但是我们要操作硬件又不得不去调用C函数,看起来这是个十分矛盾的问题。其实不然,
例如:在winds操作系统中,我们没有办法执行linux程序,但是通过虚拟机可以运行ubuntu执行linux系统程序。
同理,在andriod系统中,我们也可以创建一个虚拟机,去调用C函数,实现对底层的控制

协议制定

根据我们目的,要实现对LED灯的控制,我们至少需要两个C函数open,ioctrl。在C文件中,我们把这个两个函数实现,然后通过java虚拟机进行注册,转化为对应的方法。为了方便,我们使用一一对应的关系,即在java软件程序中调用ioctl,实质是调用C程序的ioctl,当然open,close也是一一对应。
制定一个简单的协议,ioctl传入两个参数,第一个指定LED(可传递参数1或者2),第二个参数指定LED的状态(0代表OFF,1代表ON)下面我们就开始向java虚拟机注册函数

注册本地C函数

我们先创建一个hardcontrol.c文件,这个文件主要是把C函数(主要为对硬件的操作函数)注册转化为java方法,这样andriod系统就可以通过调用java方法,执行对应的C函数,实现对硬件的操作。
首先实现函数

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved)

该函数会在APP软件程序中调用System.loadLibrary时进而被调用,那么System.loadLibrary的作用是什么呢?从名字上理解即可,就是系统加载library,至于他什么时候加载,怎么加载,在后面进行讲解。下面我们看看这个函数需要做什么

JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *jvm, void *reserved)
{
	JNIEnv *env;
	jclass cls;

	if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4)) {
		return JNI_ERR; /* JNI version not supported */
	}
	cls = (*env)->FindClass(env, "com/thisway/hardlibrary/HardControl");
	if (cls == NULL) {
		return JNI_ERR;
	}

	/* 2. map java hello <-->c c_hello */
	if ((*env)->RegisterNatives(env, cls, methods, sizeof(methods)/sizeof(methods[0])) < 0)
		return JNI_ERR;

	return JNI_VERSION_1_4;
}

其中的核心函数为 ( e n v ) &gt; R e g i s t e r N a t i v e s ( ) \color{red}{(*env)-&gt;RegisterNatives()} ,它实现了C函数到java方法的转化,这个函数需要是个参数:
JNIEnv *env: JNI的环境变量
jclass cls: 注册C程序后,生成的类相关信息保存结构体
JNINativeMethod **methods: 需要注册的C函数结构体数组
jint number:注册函数的数目
既然要实现RegisterNatives函数,那么我们就需要传递他所需要的四个参数

JNIEnv *env

JNIEnv *env;
if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4)) 

其中JNI_VERSION_1_4为JNI的版本号,这里我们暂时填写为JNI_VERSION_1_4

jclass cls

jclass cls;
cls=(*env)>FindClass(env,"com/example/administrator//hardlibrary/HardControl");

\color{red}{注意} :这里的路径比较重要,要生成一个class,这个路径表示这个class属于哪个包,在其他的class想导入生成的class必须以这个路径为基准,这个后续会再次提到。

JNINativeMethod **methods

static const JNINativeMethod methods[] = {
	{"ledOpen", "()I", (void *)ledOpen},
	{"ledClose", "()V", (void *)ledClose},
	{"ledCtrl", "(II)I", (void *)ledCtrl},
};

这个参数,为一个二级指针,我们可以一次注册多个函数,右边的"ledOpen",“ledClose”,“ledCtrl"代表的是java中的方法,注册之后我们就可以调用HardControl类中"ledOpen”,“ledClose”,“ledCtrl”。实质是调用右边与之对应的C函数,然后我们就需要实现这三个函数,具体代码在最后有贴出
由于内核驱动,还没有移植,我们暂时只做简单信息打印,其中
"()I"代表方法没有参数,返回值为int型
"()V"代表方法没有参数,也没有返回值
"(II)I"代表方法有两个int型参数和一个int型的返回值,具体了解可另行百度

动态C库编译

通过上面的介绍,我们已经写出了JNI本地函数,现在我们需要把hardcontrol.c编译成动态C库,然后加载到APP中。编译命令如下

sudo aarch64-linux-gnu-gcc hardcontrol.c -fPIC -shared -o libhardcontrol.so -I /usr/lib/jvm/java-1.8.0-openjdk-amd64/include/ -I /usr/lib/jvm/java-8-openjdk-amd64/include/linux/ -I /home/zwh/nfs/2.Work/RK3399/android/2.sourceCode/RK3399IND-pro/prebuilts/ndk/current/platforms/android-24/arch-arm64/usr/include/ -nostdlib /home/zwh/nfs/2.Work/RK3399/android/2.sourceCode/RK3399IND-pro/prebuilts/ndk/current/platforms/android-24/arch-arm64/usr/lib/libc.so /home/zwh/nfs/2.Work/RK3399/android/2.sourceCode/RK3399IND-pro/prebuilts/ndk/current/platforms/android-24/arch-arm64/usr/lib/liblog.so

命令看起来有点吓人,不过没有办法,
如果不加上
-I /usr/lib/jvm/java-1.8.0-openjdk-amd64/include/
就会出现找不到jni.h的错误提示;

如果不加上
-I /home/zwh/nfs/2.Work/RK3399/android/2.sourceCode/RK3399IND-pro/prebuilts/ndk/current/platforms/android-24/arch-arm64/usr/include/
就会出现找不到log.h的错误提示;

如果不加上
-nostdlib /home/zwh/nfs/2.Work/RK3399/android/2.sourceCode/RK3399IND-pro/prebuilts/ndk/current/platforms/android-24/arch-arm64/usr/lib/libc.so
就会出现library “libc.so.6” not found 的错误提示,系统中默认没有libc.so.6库,所以可以直接指定使用libc.so进行代替;

如果不加上
/home/zwh/nfs/2.Work/RK3399/android/2.sourceCode/RK3399IND-pro/prebuilts/ndk/current/platforms/android-24/arch-arm64/usr/lib/liblog.so
使用打印接口需要使用到liblog.so否则就会出错,该liblog.so可以在android源码中查找得到;

APP加载C库

通过前面的操作,编译生成libhardcontrol.so我们把这个C库拷贝到AS(AandriodStudio)工程目录app-> libs-> arm64-v8a(该文件自行创建)下,然后打开AS,在工程界面app-> jniLibs下可以看到arm64-v8a文件夹中的libhardcontrol.so,这样我们的这个C库,就加载到了工程中,但是加载过后我们并没有使用。

前面提到过

cls(*env)>FindClass(env,"com/example/administrator/hardlibrary/HardControl");

这里要填写"com/example/administrator/HardControl"的原因是,我们要把对硬件操作的C函数注册成一个类,这个类就是HardControl,属于包com.example.administrator,现在我们在这个包中创建文夹(工程目录app->src-> main-> java-> example-> administrator)hardlibrary,并且在其中创建文件HardControl.java,然后打开AS软件,编写该文件代码如下:

package com.example.administrator.hardlibrary;

public class  HardControl {
    public static native int ledCtrl(int which,int status);
    public static native int ledOpen();
    public static native void ledClose();

    static {
        try {
            System.loadLibrary("hardcontrol");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这里我们定义了一个public class HardControl,其中定义了三个静态本地函数ledCtrl(int which,int status),ledOpen(),ledClose()。其中有静态代码块,块中的 System.loadLibrary(“hardcontrol”)加载了我们之前编译生成的C库,前面提到过,在加载C库的时候,会执行

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved)

这样,就会注册C函数为HardControl中的方法,在APP软件程序中,需要控制LED时,只需要导入import com.example.administrator.hardlibrary.*,我们就可以调用HardControl中的方法了。

APP软件补充

在上一节中,我们的APP软件只只是进行了简单的操作,点击界面button和checkbox只做了简单的打印信息,现在我们让他通过
class HardControl 进而调用C函数,打开MainActivity.java文件(app-> java中),在class MyButtonListener implements View.OnClickListener中,添加HardControl.ledCtrl函数,具体怎么添加,请看本博客末尾的代码清单。
\color{red}{注意} :Gradle Scripts->build.gradle(Modeule:app)脚本文件中,android 代码段添加

    splits {
        abi {
            enable true
            reset()
            include 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' //select ABIs to build APKs for
            universalApk true //generate an additional APK that contains all the ABIs
        }
    }

目的是让其把libhardcontrol.so加载到APK之中,在我们的GEC-3399开发板上正常运行。

代码清单

hardcontrol.c

#include <jni.h>  /* /usr/lib/jvm/java-1.8.0-openjdk-amd64/include/ */
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>

#include <android/log.h>  /* liblog */
 
#if 0
typedef struct {
    char *name;          /* Java閲岃皟鐢ㄧ殑鍑芥暟鍚?*/
    char *signature;    /* JNI瀛楁鎻忚堪绗? 鐢ㄦ潵琛ㄧずJava閲岃皟鐢ㄧ殑鍑芥暟鐨勫弬鏁板拰杩斿洖鍊肩被鍨?*/
    void *fnPtr;          /* C璇█瀹炵幇鐨勬湰鍦板嚱鏁?*/
} JNINativeMethod;
#endif

jint ledOpen(JNIEnv *env, jobject cls)
{
	__android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "native ledOpen");
	return 0;
}

void ledClose(JNIEnv *env, jobject cls)
{
	__android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "native ledClose");
}

jint ledCtrl(JNIEnv *env, jobject cls, jint which, jint status)
{
	int ret;
	//int ret = ioctl(fd, status, which);
	__android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "native ledCtrl : %d, %d, %d", which, status, ret);
	return ret;
}


static const JNINativeMethod methods[] = {
	{"ledOpen", "()I", (void *)ledOpen},
	{"ledClose", "()V", (void *)ledClose},
	{"ledCtrl", "(II)I", (void *)ledCtrl},
};




/* System.loadLibrary */
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *jvm, void *reserved)
{
	JNIEnv *env;
	jclass cls;

	if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4)) {
		return JNI_ERR; /* JNI version not supported */
	}
	cls = (*env)->FindClass(env, "com/example/administrator/hardlibrary/HardControl");
	if (cls == NULL) {
		return JNI_ERR;
	}

	/* 2. map java hello <-->c c_hello */
	if ((*env)->RegisterNatives(env, cls, methods, sizeof(methods)/sizeof(methods[0])) < 0)
		return JNI_ERR;

	return JNI_VERSION_1_4;
}

HardControl.java

package com.example.administrator.hardlibrary;

public class  HardControl {
    public static native int ledCtrl(int which,int status);
    public static native int ledOpen();
    public static native void ledClose();

    static {
        try {
            System.loadLibrary("hardcontrol");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

回顾总结

该小节,主要介绍了,andriod软件如何加载,通过JNI调用C函数,以及生成动态C库的编译过程
下小节将介绍,内核驱动的实现

源码下载

[系统移植相关源码]
(https://github.com/944284742/android7.1Transplant.git)
[AndriodStudioAPP]
(https://github.com/944284742/andriod7.1APP.git)

猜你喜欢

转载自blog.csdn.net/weixin_43013761/article/details/86719785