java 封裝jni 資料返回 結構體傳遞 等
近期需要c和java進行資料互動,使用jni技術,網上教程也參考不少,我這裡參考一些案例 做一些彙總,幫後來人少一些彎路
win
1 直接使用vs建立dll工程,執行相關程式碼會出現找不到jni.h的問題, 這個也好做 在專案屬性介面 vc 目錄中 包含目錄 新增java的include就可以了( win這個樣子沒問題)
另一個問題是預編譯頭問題,(此處不使用預編譯頭,因為要做到程式碼在linux也照樣跑起來)
2 編譯時候切換debug和release,可能需要重新填寫以上資訊 另一點,debug生成的 dll 庫,可能導致在無vc環境的電腦無法使用 因此win對外發布時,需要使用release生成的dll庫
3 關於動態庫編譯時,標頭檔案生成 建議使用java自己生成.如下
對於java10 之前的 使用javac SendSMS.java生成.class檔案再 javach SendSMS生成.h檔案
對於java10 之後的 使用 javac -h . SendSMS.java
class SendSMS { public native int SmsInit(); public native int SmsSend(byte[] mobileNo, byte[] smContent); public native int foo(Foo fooObj); public native int SmsRead(int x,int y,double[] cText); }
自動生成的.h檔案如下 SendSMS.h
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class SendSMS */ #ifndef _Included_SendSMS #define _Included_SendSMS #ifdef __cplusplus extern "C" { #endif /* * Class: SendSMS * Method: SmsInit * Signature: ()I */ JNIEXPORT jint JNICALL Java_SendSMS_SmsInit (JNIEnv*, jobject); /* * Class: SendSMS * Method: SmsSend * Signature: ([B[B)I */ JNIEXPORT jint JNICALL Java_SendSMS_SmsSend (JNIEnv *, jobject, jbyteArray, jbyteArray); /* * Class: SendSMS * Method: foo * Signature: (LFoo;)I */ JNIEXPORT jint JNICALL Java_SendSMS_foo (JNIEnv *, jobject, jobject); /* * Class: SendSMS * Method: SmsRead * Signature: (II[D)I */ JNIEXPORT jint JNICALL Java_SendSMS_SmsRead (JNIEnv *, jobject, jint, jint, jdoubleArray); #ifdef __cplusplus } #endif #endif
後面就根據生成的這個.h開發響應的函式 如下SendSMS.c
#include <jni.h> typedef struct chuanStruts { int y; double doubletext[40]; } smsstruts; typedef struct Foo { int len; char name[100]; } Foo_t; JNIEXPORT jint JNICALL Java_SendSMS_SmsInit(JNIEnv *ev, jobject obj) { return SmsInit(); //呼叫sms.c裡的SmsInit方法 } JNIEXPORT jint JNICALL Java_SendSMS_SmsSend(JNIEnv *ev, jobject obj, jbyteArray mobileno, jbyteArray smscontent) { char * psmscontent ; //jsize thearraylengthj = (*env)->getarraylength(env,mobileno); jbyte * arraybody = (*ev)->GetByteArrayElements(ev,mobileno,0); char * pmobileno = (char *)arraybody; printf("[%s]/n ", pmobileno); //jsize size = (*env)->getarraylength(env,smscontent); arraybody = (*ev)->GetByteArrayElements(ev,smscontent,0); psmscontent = (char *)arraybody; return SmsSend(pmobileno,psmscontent); //呼叫sms.c裡的SmsSend方法 } JNIEXPORT jint JNICALL Java_SendSMS_SmsRead (JNIEnv *ev, jobject obj, jint x,jint y, jdoubleArray doubletext) { smsstruts example; //自己構建的example結構體變數 double * psmscontent ; int i; jdouble * arraybody = (*ev)->GetDoubleArrayElements(ev,doubletext,0); psmscontent = (double *)arraybody; printf("%f",*psmscontent); printf("%f",*(psmscontent+1)); example.y= y; for (i=0;i<2;i++) { example.doubletext[i] = *(psmscontent+i); } return SmsRead(x,&example); //呼叫sms.c裡的SmsRead方法 } JNIEXPORT jint JNICALL Java_SendSMS_foo(JNIEnv* env, jobject obj, jobject fooObj) { //printf("debug -10\n"); char buf[1024]; printf("debug -9\n"); memset(buf, 0x00, 1024); printf("debug -8\n"); Foo_t* bar = (Foo_t*)buf;//malloc(sizeof(Foo_t)); printf("debug -7\n"); jclass clazz; printf("debug -6\n"); jfieldID fid; printf("debug -5\n"); //init the bar data of C char* t = "Yachun Miao"; printf("Java_SendSMS_foo: %s %d\n", t, strlen(t)); strcpy(bar->name, "Yachun Miao"); printf("debug -4\n"); bar->len = strlen(bar->name); printf("Java_SendSMS_foo: %s %d\n", bar->name, bar->len); printf("debug -3\n"); // mapping bar of C to foo clazz = (*env)->GetObjectClass(env, fooObj); printf("debug -2\n"); if (0 == clazz) { printf("debug -1\n"); printf("GetObjectClass returned 0\n"); return (-1); } printf("debug 0\n"); fid = (*env)->GetFieldID(env, clazz, "len", "I"); printf("debug 1\n"); (*env)->SetLongField(env, fooObj, fid, bar->len); printf("debug 2\n"); fid = (*env)->GetFieldID(env, clazz, "name", "Ljava/lang/String;"); printf("debug 3\n"); jstring name = (*env)->NewStringUTF(env, bar->name); printf("debug 4\n"); (*env)->SetObjectField(env, fooObj, fid, name); printf("debug 5\n"); //free(bar); return 0; }
開發時,其他檔案如下sms.h
#ifndef _TX_SMS_H_ #define _TX_SMS_H_ #ifdef __cplusplu* extern "C" { #endif typedef struct tagSmsEntry { int index; double text[40]; } SmsEntry; int SmsInit(void); //無引數 int SmsSend(char *phonenum, char *content); //指標變數引數 int SmsRead(int x,SmsEntry *entry); //結構體引數 #ifdef __cplusplus } #endif #endif
sms.c
#include "sms.h" int SmsInit(void) { printf("welcome \n"); return 1; } int SmsSend(char *phonenum, char *content) { printf("liuxiao \n"); char a[100]; memset(a, 0x00, 100); memset(a, 0x31, 99); printf("%s\n",a); printf("%s %s\n",phonenum,content); return 2; } int SmsRead(int x,SmsEntry *entry) { int i; printf("mingxin\n"); printf("%d \n",x); for (i=0;i<2;i++) { printf("%f ",entry->text[i]); } return 3; }
注意通過經驗總結髮現,不能使用malloc(sizeof(Foo_t))
使用之後,程式直接崩潰,所以只能使用臨時陣列,具體原因有待考究
4 生成動態庫時點選 生成->生成解決方案
linux
1 生成庫檔案時,前面必須新增lib(感覺就是一xx操作)
2 安裝java時,不要使用yum自帶的安裝,裡面內容不全,沒有include標頭檔案,因此需要自己到官網下載
具體指令如下
mkdir usr/local/java cd usr/local/java wget https://download.oracle.com/java/17/latest/jdk-17_linux-x64_bin.tar.gz tar -zxvf jdk-17_linux-x64_bin.tar.gz
然後就是修改 配置檔案 追加 內容
JAVA_HOME=/usr/local/java/jdk-17.0.2 PATH=$PATH:$JAVA_HOME/bin CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar export JAVA_HOME CLASSPATH PATH
最後source /etc/profile
檢驗java -version
如果不對,就 which java 檢視當前java 路徑 再rm -rf 路徑 此時,應該就可以看到版本號正確了
2 編譯 注意 引入外部標頭檔案時候 是大寫的字母i 這個很坑,換成小寫報錯,只能大寫,但是和小寫字母L太像了注意 生成.so檔案時,必須在檔案前加上lib 不加將導致程式不識別(網上說了很多,但是沒幾個提到這一點的)
3 執行java程式時, java -Djava.library.path=. SendSMS.java
相關程式碼如下
win vs2019 工程
https://files.cnblogs.com/files/RYSBlog/JCProtocol2.zip?t=1643077628
linux
https://files.cnblogs.com/files/RYSBlog/linuxso.zip?t=1643077619
//