【NDK】【003】JNI調用dll動態庫

JNI調用C++規則

  • Java方法前要用native關鍵字修飾,並且只有方法聲明,沒有實現,因爲實現代碼在C++裏面
  • C++方法前要用JNIEXPORT和JNICALL宏修飾
  • C++方法中的數據類型,要使用與Java數據類型相對應的JNI類型
  • C++方法名必須完全符合Java_JavaPackageName_JavaClassName_JavaFunctionName的格式
  • Java中的native方法可以是static的,也可以是static的,只要方法名匹配即可

使用IntelliJ Idea和CLion開發JNI

從零開始搭建並運行一個JNI項目並不難
但對於新手來說,每個小細節都可能出錯,靠自己嘗試出來可能要浪費大量時間
所以這裏還是不辭繁瑣,細講一遍

本次使用的開發工具是IntelliJ Idea和CLion,它們分別是Java和C++的開發環境
我們以後的NDK開發,和這個流程基本一致,只不過NDK所有工作都可以在AndroidStudio中完成
Android項目比較複雜,而我們現在演示的JNI項目則十分簡單,除了必需的代碼文件,幾乎沒什麼多餘的干擾信息
這樣更方便我們弄清Java/C++混編背後的運作原理
雖然dll開發不是安卓開發者必須掌握的,但這其中包含了很多共通性的原理,對大家長遠發展很有幫助

工作中時常會看到很多人,只要一接觸到複雜點的開發工具,編譯環境,或是編譯失敗,就開始煩躁放棄
其實這就是平時只關心業務功能實現,對軟件的編譯、運行、協作原理了解太少的原因
由於不瞭解通用原理,對新事物的分析解決能力很弱,更別提自己去對開發環境進行設計或優化了

回到正題,我們開始講解,如何從零開始,搭建並運行一個JNI項目

在IntelliJ Idea中創建一個空的Java項目,創建一個類文件,在其中添加我們想要的native方法
IntelliJ Idea中的藍色文件夾表示源碼目錄,黃色文件夾表示編譯配置等非代碼目錄
我們可以看到,這個項目就一個src文件夾放源碼,一個libs文件夾放dll庫,結構非常簡單
在這裏插入圖片描述
由於JNI對C++文件格式的要求比較複雜,我們自己生成比較麻煩且容易出錯,所以需要藉助工具來完成
JDK默認就提供了一個javah指令,用於根據Java文件中的native接口,直接生成C++頭文件

我們在src文件夾處右鍵,打開Terminal控制檯,然後執行javah指令
Terminal是IntelliJ Idea內戰的快捷控制檯,可以在裏面執行cmd指令
我們在src處打開Terminal,cmd默認就會自動切換到src目錄處,十分方便
注意javah指令的執行目錄,還有類名要包括包名,這兩點都不能錯
在這裏插入圖片描述
在這裏插入圖片描述
我們已經得到了C++頭文件,接下來我們要實現頭文件中的接口方法,並編譯成dll文件

由於頭文件中用到的數據類型都是JNI類型,這在原生C++中是沒有的,所以我們要把定義JNI數據類型的文件一起拷貝到C++工程中
JNI數據類型的定義需要使用jni.h和jni_md.h這兩個文件,可以在jdk/include目錄下找到

在CLion中創建一個新的C++ Libarary工程,將生成的JNI頭文件和JNI數據類型定義文件一起拷入
創建一個cpp文件,實現JNI頭文件中定義的方法
在這裏插入圖片描述
如圖所示,C++工程和上面的Java工程一樣,結構非常簡單
到此爲止,C++代碼已經編寫完成了,接着我們要將C++代碼編譯爲dll文件
由於我們添加了新的代碼文件,所以需要修改編譯腳本,即CMakeLists.txt中的編譯文件列表
在這裏插入圖片描述
Build - Rebuild Project,編譯dll文件
在這裏插入圖片描述
現在只要把dll文件拷到Java項目裏面就可以運行了
dll存放的位置是和工程配置有關係的,如果沒有修改過配置,默認放到工程根目錄下就行了
在這裏插入圖片描述
dll全部放在外面看起來很亂,如果我們想把dll全部放到libs文件夾下面該怎麼設置呢?
網上有的教程又會叫我們把dll放到JDK或C盤下面,爲什麼這樣也可以?
爲什麼有時明明按照網上教程來了,但就是dll加載失敗?
爲什麼明明項目可以運行,打包成jar包後,dll就加載失敗了?
其實我們剛剛已經說了,dll存放位置是取決於工程配置的
網上很多dll使用教程是按他自己環境來的,所以到了別人機子上就不合適了
所以我們需要弄清楚dll的加載原理,下一篇博客我們會專門來講解這個事情

其實這個事情本身比較簡單,之所以我要講這個,是看到有些40多歲的工程師,還會出現dll找不到不知道該怎麼辦的問題,說明不求甚解的人實在是不少,所以決定專門寫個博客來講下,如果有人正好看到,以後就可以少走彎路

源代碼


	//Hello.java

	package com.easing.java;
	
	public class Hello {
	
	    static {
	        System.loadLibrary("libhello");
	    }
	
	    public static native int sum(int a, int b);
	
	    public static void main(String[] args) {
	        Hello instance = new Hello();
	        int sum = instance.sum(100, 200);
	        System.out.println("sum result is : " + sum);
	    }
	}


	//com_easing_java_Hello.h

	#ifndef _Included_com_easing_java_Hello
	#define _Included_com_easing_java_Hello
	
	#include "jni.h"
	
	#ifdef __cplusplus
	    extern "C" {
	#endif
	        JNIEXPORT jint JNICALL Java_com_easing_java_Hello_sum(JNIEnv *, jobject, jint, jint);
	#ifdef __cplusplus
	    }
	#endif

	#endif


	//com_easing_java_Hello.cpp

	#include "com_easing_java_Hello.h"
	
	JNIEXPORT jint JNICALL Java_com_easing_java_Hello_sum(JNIEnv *env, jobject obj, jint a, jint b) {
	    return a + b;
	}


	//CMakeLists.txt

	set(CMAKE_CXX_STANDARD 17)
	
	project(hello)
	
	add_library(
	        hello SHARED
	        jni.h
	        jni_md.h
	        com_easing_java_Hello.h
	        com_easing_java_Hello.cpp
	)


發佈了430 篇原創文章 · 獲贊 43 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章