一、JNA技術的難點
上篇文章我們成功實現了Java使用JNA調用C/C++的函數代碼:
int sayHello(){
printf("Hello World!");
return 1;
}
上面的代碼非常簡單,在控制臺打印輸入"Hello World",并返回整數型。然后我們在CLibrary中也是定義了一個對應的函數
int sayHello();
由于C/C++語言和Java語言中的int類型對應,所以這里并沒有復雜的類型轉換,也就大大降低了調用JNA和C/C++代碼對接的難度。
有過跨語言、跨平臺開發的程序員都知道,跨平臺、語言調用的難點,就是不同語言之間數據類型不一致造成的問題。絕大部分跨平臺調用的失敗,都是這個問題造成的。關于這一點,不論何種語言,何種技術方案,都無法解決這個問題。JNA也不例外。
上面說到接口中使用的函數必須與鏈接庫中的函數原型保持一致,這是JNA甚至所有跨平臺調用的難點,因為C/C++的類型與Java的類型是不一樣的,你必須轉換類型讓它們保持一致,比如printf函數在C中的原型為:
void printf(const char *format, [argument]);
你不可能在Java中也這么寫,Java中是沒有char *指針類型的,因此const char * 轉到Java下就是String類型了。
二、JNA的常用類型映射(Type Mappings)如下:
Native Type | Java Type | Native Representation |
---|---|---|
char | byte | 8-bit integer |
wchar_t | char | 16/32-bit character |
short | short | 16-bit integer |
int | int | 32-bit integer |
int | boolean | 32-bit integer (customizable) |
long, __int64 | long | 64-bit integer |
long long | long | 64-bit integer |
float | float | 32-bit FP |
double | double | 64-bit FP |
pointer | Buffer/Pointer | |
pointer array | [] (array of primitive type) | |
char* | String | |
wchar_t* | WString | |
char** | String[] | |
wchar_t** | WString[] | |
void* | Pointer | |
void ** | PointerByReference | |
int& | IntByReference | |
int* | IntByReference | |
struct | Structure | |
(*fp)() | Callback | |
varies | NativeMapped | |
long | NativeLong | |
pointer | PointerType |
附帶一個很重要的API接口文檔地址:傳送門
三、具體實例
接下來我們寫一個復雜一點的C++函數
bool checksum(const char* src_data, unsigned short& check_ret)
然后在Java中調用該方法,傳入具體參數,并獲取到返回值。
1、首先是check.h文件和check.cpp文件:
- check.h
#ifndef TASK_CHECKSUM_H
#define TASK_CHECKSUM_H
typedef signed char int8_t;
typedef short int int16_t;
typedef int int32_t;
/* --------------------------------------------------------------------------*/
/**
* @Synopsis checksum加密算法
*
* @Param src_data 被加密的字符串
*
* @Param check_ret int16_t類型的加密結果
*
* @Returns 加密是否成功
*/
/* ----------------------------------------------------------------------------*/
extern "C" bool checksum(const char* src_data, unsigned short& check_ret);
#endif
- check.cpp
#include "check.h"
#include <string.h>
extern "C"{
bool checksum(const char* src_data, unsigned short& check_ret) {
bool ret = true;
do {
const int16_t* opt_data = reinterpret_cast<const int16_t*>(src_data);
if (opt_data == NULL || src_data == NULL) {
ret = false;
/*TODO*/
// warn_log();
break;
}
int32_t accu_sum = 0;
int32_t data_len = strlen(src_data);
while (data_len > 1) {
accu_sum += *(opt_data);
opt_data++;
data_len = data_len - 2;
if (accu_sum & 0x80000000) {
accu_sum = (accu_sum >> 16) + (accu_sum & 0xFFFF);
}
}
if (data_len == 1) {
accu_sum += *(reinterpret_cast<const int8_t*>(opt_data));
}
while (accu_sum >> 16) {
accu_sum = (accu_sum >> 16) + (accu_sum & 0xFFFF);
}
check_ret = 0xFFFF & accu_sum;
// check_ret = (accu_sum == 0xFFFF)?~accu_sum:accu_sum;
// check_ret = (accu_sum == 0xFFFF)?accu_sum:~accu_sum;
}while(0);
return ret;
}
}
上述代碼中checksum函數需要用戶傳入一個char*指針類型的參數src_data和一個short&引用類型的check_ret,代碼對stc_data進行
加密操作之后,把加密結果返回到參數check_ret中,函數返回加密是否成功的標記。由于代碼中使用了string,bool等和C++相關的元素,
所以我們的文件后綴一定要使用.cpp,并且在整個函數外部使用了 extern "C" 給C++代碼做標記,否則在Java調用該方法的時候會提示無法找到。
2、然后是CLibrary對象和MainActivity對象
編寫完畢代碼,使用Ndk-build命令行進行編譯,成功生成libcheck.so文件,配置完畢JNA的相關jar包和庫文件,把他們添加到已經準備好的jniLibs文件夾中,然后創建按一個CLibrary類:
//繼承Library,用于加載庫文件
public interface Clibrary extends Library {
//加載libhello.so鏈接庫
Clibrary INSTANTCE = (Clibrary) Native.loadLibrary("check", Clibrary.class);
//此方法為鏈接庫中的方法
// checksum(const char* src_data, unsigned short& check_ret)
int checksum(String src_data, IntByReference check_ret);
}
MainActivity
package com.afinalstone.androidstudy.myjna_02;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.PointerByReference;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void onClick(View view){
IntByReference check_ret = new IntByReference();
int flag = Clibrary.INSTANTCE.checksum("123",check_ret);
Log.d("MainActivity","checksum的返回標記:"+flag);
Log.d("MainActivity","checksum的返回結果:"+check_ret.getValue());
}
}
代碼定義了一個點擊事件,點擊按鈕調用 int flag = Clibrary.INSTANTCE.checksum("123",check_ret),并輸出checksum的返回標記和加密結果。
項目地址:傳送門