JNA實戰(zhàn)筆記匯總<一> 簡單認識JNA|成功調用JNA

一、簡介

先說JNI(Java Native Interface)吧,有過不同語言間通信經歷的一般都知道,它允許Java代碼和其他語言(尤其C/C++)寫的代碼進行交互,只要遵守調用約定即可。首先看下JNI調用C/C++的過程,注意寫程序時自下而上,調用時自上而下。

JNI調用過程

可 見步驟非常的多,很麻煩,使用JNI調用.dll/.so共享庫都能體會到這個痛苦的過程。如果已有一個編譯好的.dll/.so文件,如果使用JNI技 術調用,我們首先需要使用C語言另外寫一個.dll/.so共享庫,使用SUN規(guī)定的數據結構替代C語言的數據結構,調用已有的 dll/so中公布的函 數。然后再在Java中載入這個庫dll/so,最后編寫Java native函數作為鏈接庫中函數的代理。經過這些繁瑣的步驟才能在Java中調用 本地代碼。因此,很少有Java程序員愿意編寫調用dll/.so庫中原生函數的java程序。這也使Java語言在客戶端上乏善可陳,可以說JNI是 Java的一大弱點!

那么JNA是什么呢?

JNA(Java Native Access)框架是一個開源的Java框架,是SUN公司主導開發(fā)的,建立在經典的JNI的基礎之上的一個框架。使用JNI調用共享類庫(.dll/.so文件)是非常麻煩的事情,既需要編寫java代碼,又要編寫C語言的代理方法,這其中需要很多數據類型的轉換,是讓人非常頭痛。JNA框架就是為了解決這些問題和繁瑣的事情而開發(fā)的,它提供一組Java工具類用于在運行期動態(tài)訪問系統本地共享類庫而不需要編寫任何Native/JNI代碼。開發(fā)人員只要在一個java接口中描述目標native library的函數與結構,JNA將自動實現Java接口到native function的映射,大大降低了Java調用本體共享庫的開發(fā)難度。JNA與.NET平臺上的P/Invoke機制一樣簡單和方便。
之所以說它是JNI的替 代者,是因為JNA大大簡化了調用本地方法的過程,使用很方便,基本上不需要脫離Java環(huán)境就可以完成。

如果要和上圖做個比較,那么JNA調用C/C++的過程大致如下:

JNA調用過程

可以看到步驟減少了很多,最重要的是我們不需要重寫我們的動態(tài)鏈接庫文件,而是有直接調用的API,大大簡化了我們的工作量。

JNA只需要我們寫Java代碼而不用寫JNI或本地代碼。功能相對于Windows的Platform/Invoke和Python的ctypes。

二、原理

JNA使用一個小型的JNI庫插樁程序來動態(tài)調用本地代碼。開發(fā)者使用Java接口描述目標本地庫的功能和結構,這使得它很容易利用本機平臺的功能,而不會產生多平臺配置和生成JNI代碼的高開銷。這樣的性能、準確性和易用性顯然受到很大的重視。

此外,JNA包括一個已與許多本地函數映射的平臺庫,以及一組簡化本地訪問的公用接口。

注意:

JNA是建立在JNI技術基礎之上的一個Java類庫,它使您可以方便地使用java直接訪問動態(tài)鏈接庫中的函數。

原來使用JNI,你必須手工用C寫一個動態(tài)鏈接庫,在C語言中映射Java的數據類型。

JNA中,它提供了一個動態(tài)的C語言編寫的轉發(fā)器,可以自動實現Java和C的數據類型映射,你不再需要編寫C動態(tài)鏈接庫。

也許這也意味著,使用JNA技術比使用JNI技術調用動態(tài)鏈接庫會有些微的性能損失。但總體影響不大,因為JNA也避免了JNI的一些平臺配置的開銷。

三、相關jna.jar包和.so庫文件的下載

JNA的項目是放在Github上面的,目前最新版本是4.4.0,已有打包好的jar文件可供下載。
我這里使用的是4.2.1版本的,并提供了可以直接使用的jar包和.so庫文件,JNA開發(fā)的jna.jar以及.so庫文件

四、配置環(huán)境,編譯sayhello.so庫文件

1、在app下面建立一個jni文件夾,添加庫函數文件sayhello.c:

#include "sayhello.h"

int sayHello(){
    printf("Hello World!");
    return 1;
}

以及頭文件sayhello.h:

#include <stdio.h>
int sayHello();

2、其他就是配置我們編譯C/C++代碼的環(huán)境了,如果不懂可以參考這篇文章:NDK學習筆記<一> 初步認識JNI|成功搭建NDK開發(fā)環(huán)境

3、成功配置我們的C/C++編譯環(huán)境后,在lib目錄下面成功編譯出我們需要的.so文件。然后把jna.jar和相關的.so文件添加到項目中

4、接著我們在src目錄下面添加jniLibs文件夾:把JNA的libjnidispatch.so.so庫文件和我們生成的libsayhello.so庫文件全部添加合并到jniLibs
文件夾下面,并配置我們的build.gradle文件,添加jniLibs.srcDirs = ['src/main/jniLibs']:

android {
    compileSdkVersion 25
    buildToolsVersion "26.0.1"

    defaultConfig {
        applicationId "com.afinalstone.androidstudy.myjna_01"
        minSdkVersion 16
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    sourceSets {
        main {
            jniLibs.srcDirs = ['src/main/jniLibs']
        }
    }
}

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile 'com.android.support:appcompat-v7:25.3.1'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
}

五、創(chuàng)建Library,成功實現Java調用C/C++函數代碼庫

創(chuàng)建一個Clibrary對象:

package com.afinalstone.androidstudy.myjna_01;

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.PointerByReference;

//繼承Library,用于加載庫文件
public interface Clibrary extends Library {
    //加載libhello.so鏈接庫
    Clibrary INSTANTCE = (Clibrary) Native.loadLibrary("sayhello", Clibrary.class);
    //此方法為鏈接庫中的方法

    int sayHello();
}

1、定義接口對象。

在上面的代碼中,我們定義了一個接口,繼承自Library 或StdCallLibrary,默認的是繼承Library ,
如果動態(tài)鏈接庫里的函數是以stdcall方式輸出的,那么就繼承StdCallLibrary,比如眾所周知的kernel32庫。

2、接口內部定義

接口內部需要一個公共靜態(tài)常量:INSTANCE,通過這個常量,就可以獲得這個接口的實例,從而使用接口的方法,也就是調用外部dll/so的函數。
該常量通過Native.loadLibrary()這個API函數獲得,該函數有2個參數:

  • 第一個參數是動態(tài)鏈接庫dll/so的名稱,但不帶.dll或.so這樣的后綴,這符合JNI的規(guī)范,因為帶了后綴名就不可以跨操作系統平臺了。搜索動態(tài)鏈 接庫路徑的順序是:先從當前類的當前文件夾找,如果沒有找到,再在工程當前文件夾下面找win32/win64文件夾,找到后搜索對應的dll文件,如果 找不到再到WINDOWS下面去搜索,再找不到就會拋異常了。比如上例中printf函數在Windows平臺下所在的dll庫名稱是msvcrt,而在 其它平臺如Linux下的so庫名稱是c。

  • 第二個參數是本接口的Class類型。JNA通過這個Class類型,根據指定的.dll/.so文件,動態(tài)創(chuàng)建接口的實例。該實例由JNA通過反射自動生成。
    然后在MainActivity中調用sayHello方法:

接口中只需要定義你要用到的函數或者公共變量,不需要的可以不定義,注意參數和返回值的類型,應該和鏈接庫中的函數類型保持一致。

定義好接口后,就可以使用接口中的函數即相應dll/so中的函數了

六、調用C++函數代碼

定義好接口后,就可以使用接口中的函數即相應函數庫中的函數了,這里我們在MainActivity中響應點擊事件調用C++函數代碼,并打印出結果:


package com.afinalstone.androidstudy.myjna_01;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

    }

    //按鈕點擊事件
    public void onClick(View view){
        int result = Clibrary.INSTANTCE.sayHello();
        Log.d("MainActivity","sayHello的放回結果:"+result);
    }

}

到這里位置,如果成功運行項目,整個項目結構以及結果基本是這樣的:

結果

七、避免錯誤的建議:

1.要注意我們生成的庫文件都是lib**.so,但是我們在加載.so庫文件的時候是不需要lib前綴的。

2.要檢查build.gradle中是否指定了.so庫文件的地址,在這里我之所以吧所有的.so庫文件都放在了jniLibs文件夾中,
是因為我在build.gradle中指定了 jniLibs.srcDirs = ['src/main/jniLibs']

3.可以解壓成功編譯的apk,查看該apk的lib目錄下面是否有l(wèi)ibsayhello.so和libjnidispatch.so兩個庫文件。

4.項目中偶時候需要用到Java調用c++函數代碼,但是始終出錯,主要錯誤原因是undefined symbol,找不到c++ 方法。
需要我們使用extern "C" 給C++代碼做標記,否則無法找到。

#include <stdlib.h>  
#include <iostream>  
using namespace std;  
  
extern "C"  
{  
    void test() {  
         cout << "TEST" << endl;  
    }  
  
    int addTest(int a,int b)  
    {  
      int c = a + b ;  
      return c ;  
    }   
}  

項目地址:傳送門

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 在我們Java開發(fā)中,有些時候會涉及到跨語言的調用,比如涉及到一些高效計算、圖形渲染、加密和解密的時候會用到C++...
    程序猿猩球閱讀 1,976評論 0 1
  • Java Native Interface (JNI)是一個本地編程接口,可以讓Java代碼使用以其他語言(C/C...
    wangdy12閱讀 7,294評論 0 4
  • 前言 網上關于 Android 集成 FFmpeg 的文章很多,但大多數都只介紹了步驟,沒有說明背后的原理,若之前...
    王英豪閱讀 5,966評論 18 71
  • 1. 環(huán)境準備 A. GCC 在控制臺中輸入 如果提示命令未找到,那么說明你的計算機中還沒有gcc,去安裝一個吧,...
    Chole121閱讀 1,380評論 1 9
  • 柳志光,男,漢族。山東省棲霞市人,大專文化。1926年生,1944年參加八路軍,1946年入黨。在部隊歷任文書、文...
    柳育龍閱讀 211評論 0 0