近期需要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
//