基于jni实现c/c++调用jar包

一. 准备环境

#检查jdk版本
rpm -qa |grep java
#如果没有openjdk-devel包,执行:
yum install java-1.7.0-openjdk-devel -y
#查找JAVA_HOME
rpm -ql java-1.7.0-openjdk-devel-1.7.0.241-2.6.20.0.el7_7.x86_64
#将JAVA_HOME加入环境变量
echo "export JAVA_HOME='/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.241-2.6.20.0.el7_7.x86_64'" >> /etc/profile
source /etc/profile

并在/etc/ld.so.conf.d目录下,创建一个jni.conf,向其中写入:

/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.241-2.6.20.0.el7_7.x86_64/jre/lib/amd64/server
/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.241-2.6.20.0.el7_7.x86_64/jre/lib/amd64/

执行ldconfig -v |grep libjava,如果有结果显示,就说明配置正确。

如果没配置好,可能会出现如下问题:

Error occurred during initialization of VM
java/lang/NoClassDefFoundError: java/lang/Object

二. 编写代码如下:

#include <jni.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>  
 
/*C字符串转JNI字符串*/
jstring stoJstring(JNIEnv* env, const char* pat) {
        jclass strClass = (*env)->FindClass(env, "Ljava/lang/String;");
        jmethodID ctorID = (*env)->GetMethodID(env, strClass, "<init>",
                "([BLjava/lang/String;)V");
        jbyteArray bytes = (*env)->NewByteArray(env, strlen(pat));
        (*env)->SetByteArrayRegion(env, bytes, 0, strlen(pat), (jbyte*)pat);
        jstring encoding = (*env)->NewStringUTF(env, "utf-8");
        return (jstring)(*env)->NewObject(env, strClass, ctorID, bytes,
                encoding);
}

/*JNI字符串转C字符串*/
char* jstringTostring(JNIEnv* env, jstring jstr) {
        char* rtn = NULL;
        jclass clsstring = (*env)->FindClass(env, "java/lang/String");
        jstring strencode = (*env)->NewStringUTF(env, "utf-8");
        jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",
                "(Ljava/lang/String;)[B");
        jbyteArray barr= (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid,
                strencode);
        jsize alen = (*env)->GetArrayLength(env, barr);
        jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
        if (alen> 0) {
                rtn = (char*)malloc(alen + 1);
                memcpy(rtn, ba, alen);
                rtn[alen] = 0;
        }
        (*env)->ReleaseByteArrayElements(env, barr, ba, 0);
        return rtn;
}

int test(char* strPath) {

        int res = 0;
        JavaVM *jvm;
        JNIEnv *env;
        JavaVMInitArgs vm_args;
        JavaVMOption options[3];
        /*设置初始化参数*/
        options[0].optionString = "-Djava.compiler=NONE";  
        options[1].optionString = "-Djava.class.path=.:${JAVA_HOME}:/tmp/test-1.0.jar";  //这里指定了要使用的第三方Jar包
        options[2].optionString = "-verbose:NONE"; //用于跟踪运行时的信息
  
        /*版本号设置不能漏*/
        vm_args.version=JNI_VERSION_1_4;//jdk版本目前有1.1,1.2,1.4 只要比你的jdk的版本低就可以 我用的是openjdk1.7的版本
        vm_args.nOptions = 3;
        vm_args.options = options;
        vm_args.ignoreUnrecognized = JNI_TRUE;
        res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
        if (res < 0 || jvm == NULL || env == NULL) {
                fprintf(stderr, "Can't create Java VM\n");
                return -1;
        }
        
        fprintf(stdout, "ok 调用JNI_CreateJavaVM创建虚拟机\n");
 
        /*获取实例的类定义*/
        jclass cls = (*env)->FindClass(env, "test/myTest");    //这里是jar包内myTest类的具体路径
        if (cls == 0) {
                fprintf(stderr, "FindClass failed\n");
                (*jvm)->DestroyJavaVM(jvm);
                fprintf(stdout, "Java VM destory.\n");
                return -1;
        }
        fprintf(stdout, "ok 返回JAVA类的CLASS对象\n");
 
        /*创建对象实例*/
        jobject obj = (*env)->AllocObject(env, cls); 
        if (obj == NULL) {
                fprintf(stderr, "AllocObject failed\n");
                (*jvm)->DestroyJavaVM(jvm);
                fprintf(stdout, "Java VM destory.\n");
                return -1;
        }
        fprintf(stdout, "ok 获取该类的实例\n");
 
        /*获取构造函数,用于创建对象*/
        /***1.1可用""作为构造函数, 1.2用"<init>"参数中不能有空格
        "(Ljava/lang/String;)V"*/
        jmethodID mid = (*env)->GetMethodID(env, cls, "setParam",
                "(Ljava/lang/String;)V");             //(Ljava/lang/String;)代表传入参数为String类型,V代表返回值为void
        if (mid == 0) {
                fprintf(stderr, "GetMethodID failed\n");
                (*jvm)->DestroyJavaVM(jvm);
                fprintf(stdout, "Java VM destory.\n");
                return -1;
        }
        fprintf(stdout, "ok 获取类中的方法\n");

        jstring path = stoJstring(env, strPath);   //将char*转换为java对应的数据类型

        //jlong kpje = (jlong)atoi(argv[4]);
        (*env)->CallObjectMethod(env, obj, mid, path);  

        //(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)代表三个入参数,且都为String类型,Z代表返回值为boolean类型
        mid = (*env)->GetMethodID(env, cls, "test",
                "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z");  
        if (mid == 0) {
                fprintf(stderr, "GetMethodID failed\n");
                (*jvm)->DestroyJavaVM(jvm);
                fprintf(stdout, "Java VM destory.\n");
                return -1;
        }
        fprintf(stdout, "ok 获取类中的方法\n");

        //调用,并判断返回值
        jboolean ret = (jboolean) (*env)->CallBooleanMethod(env, obj, mid, "test1", "test2", test3);  
        if (ret == 0) {
            fprintf(stderr, "failed!!!\n");
            res = -1;
        }else{
            fprintf(stderr, "OK!!!\n");
			res = 0;
        }

        /*捕捉异常*/
        if ((*env)->ExceptionOccurred(env)) {
                /* 如果有异常,打印调用栈信息 */
                (*env)->ExceptionDescribe(env);
                return -1;
        }
        /*销毁JAVA虚拟机*/
        (*jvm)->DestroyJavaVM(jvm);
        fprintf(stdout, "Java VM destory.\n");
        return res;
}


int main(int argc, char **argv) {
        int ret = test("mypath");
        fprintf(stderr, "RET: %d\n", ret);
}

编译指令为:

gcc -o jni   jni.c -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux -L${JAVA_HOME}/jre/lib/amd64/server -ljvm

编译成功后,生成jni可执行文件。

三、数据类型的转换

主要数据类型的对应关系如下所示:

|java类型   | jni本地类型 |   c语言
|int        | jint        | int(32bit)
|boolean    | jboolean    | unsigned char(8bit)
|float      | jfloat      | float(32bit)
|char       | jchar       | unsigned short(16bit)
|byte       | jbyte       | char(8bit)
|String	    | jString	  | 无
|int[]	    | jintArray	  | int[]

其中,java的String类型与c/c++的char*互相转换的代码入下所示:

/*C字符串转JNI字符串*/
jstring stoJstring(JNIEnv* env, const char* pat) {
        jclass strClass = (*env)->FindClass(env, "Ljava/lang/String;");
        jmethodID ctorID = (*env)->GetMethodID(env, strClass, "<init>",
                "([BLjava/lang/String;)V");
        jbyteArray bytes = (*env)->NewByteArray(env, strlen(pat));
        (*env)->SetByteArrayRegion(env, bytes, 0, strlen(pat), (jbyte*)pat);
        jstring encoding = (*env)->NewStringUTF(env, "utf-8");
        return (jstring)(*env)->NewObject(env, strClass, ctorID, bytes,
                encoding);
}

/*JNI字符串转C字符串*/
char* jstringTostring(JNIEnv* env, jstring jstr) {
        char* rtn = NULL;
        jclass clsstring = (*env)->FindClass(env, "java/lang/String");
        jstring strencode = (*env)->NewStringUTF(env, "utf-8");
        jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",
                "(Ljava/lang/String;)[B");
        jbyteArray barr= (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid,
                strencode);
        jsize alen = (*env)->GetArrayLength(env, barr);
        jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
        if (alen> 0) {
                rtn = (char*)malloc(alen + 1);
                memcpy(rtn, ba, alen);
                rtn[alen] = 0;
        }
        (*env)->ReleaseByteArrayElements(env, barr, ba, 0);
        return rtn;
}

四、GetMethodID函数

jni通过GetMethod函数获取java类中的成员函数,是以"(*)+"形式表示函数的有哪些传入参数,传入参数的类型,返回值的类型。"()" 中的字符表示传入参数,后面的则代表返回值。
例如:
"()V" 就表示void Func();
"(II)V" 表示 void Func(int, int);
"(Ljava/lang/String;Ljava/lang/String;)I".表示 int Func(String,String)

"I"表示该变量是Int类型,"Ljava/lang/String;"表示该变量是String类型,

其返回值类型的对应关系如下:

|Java 类型|符号|
| Boolean |Z   |
| Byte    |B   |
| Char    |C   |
| Short   |S   |
| Int     |I   |
| Long    |L   |
| Float   |F   |
| Double  |D   |
| Void    |V   |

 

注意:

   确保只有一个线程调用这个方法并且确保只创建一个hotspot vm 实例。因为hotspot vm
   创建的静态数据结构无法再次初始化,所以一旦初始化到某个确定点后,进程空里只能有一
   个hotspot vm。
 

 

参考:

https://www.cnblogs.com/zhujiabin/p/10558051.html

https://www.jianshu.com/p/caad51c9cc34

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值