天天看點

第一章: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函數,實作對硬體的操作。

首先實作函數

該函數會在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()} (∗env)−>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庫,就加載到了工程中,但是加載過後我們并沒有使用。

前面提到過

這裡要填寫"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庫的時候,會執行

這樣,就會注冊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)

繼續閱讀