LINUX下JNI封装 C++动态链接库

本文详细介绍了如何在Linux环境下使用JNI技术封装C++动态库供Java调用,包括创建Java项目、编写Java类、编译Java源码生成JNI头文件,以及在C++中实现JNI接口的方法,特别强调了参数传递和类型转换的注意事项。

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

这几天用jni封装了一个C++库文件,供java调用,在此把具体的步骤给理一下,方便自己温故,也供大家参考。
前提:已有一个动态库文件libTest.so和包含函数声明的头文件test.h。

1. 创建java项目:JProj
在头文件test.h中包含了类类型和接口函数。如头文件test.h:

#ifndef TEST_H
#define TEST_H

typedef struct _ST_1
{
    int *a;
    long b;
    char c;
}ST1;

typedef struct _ST_2
{
    int a[];
    ST1 b; 
}ST2;

typedef struct _ST_3
{
    ST1 a[];
    ST2 b;
    int *c;
    string d;
}ST3;
void *func1(int a,ST1 b);
void func2(void *a,ST2 &b);
int func3(int *a,ST3 *b);
#endif // TEST_H

为了能在java程序中调用相关的接口,我们也需要在java中创建相应的类类型和接口函数(可在一个文件或不同文件,在此为一个文件)。
在java项目中,添加一个包:com,在包下创建两个类:JDefine.java,JTest.java,其中JDefine.java用来类类型和接口函数。JDefine.java应写为:

public class JDefine
{
    class clazz1
    {
        int[] a;     //对应于C++中的int *a;
        long b;
        byte c;     //对应于C++中的char c;
    };

    class clazz2
    {
        int[] a;
        clazz1 b;        
    };
    class clazz3
    {
        clazz1[] a;
        clazz2 b;
        int[] c;
        string d;
    };

    public native long func1(int a,clazz1 b);
    public native void func2(long a,clazz2 b);
    public native int func3(int[] a,clazz3 b);
};
  • 注意一:java程序想要通过jni调用C++的库,必须在函数声明时使用关键字native,意为此函数是本地函数,即C++库里的函数。
  • 注意二:java没有指针概念,所以可以用一个数组名来代表相同类型的指针,如定义int [] a和int *a,a代表的都是指针。但如果C++中指针为void *handle,则在java可写为long handle,直接用long型的handle表示地址数值。
  • 注意三:java没有struct,所以在C++中的结构可以用java中的class来对应。
  • 注意四:char 类型在C++中为1个字节,但在java中为2个字节,所以C++的char类型的成员映射到java中,应转变为byte。

2. 编译
写好JDefine.java好后,对其进行编译:
1、进入JDefine.java当前目录,命令行:

javac JDefine.java

2、进入com的上一级目录,一般为src,在此目录下进行命令行:

javah -classpath . -d ./com com.JDefine

可在com目录下得到com_JDefine.h文件,里面包含了用jni格式写的与JDefine.java相对应的函数声明和类对象,如函数名long func1(int a,clazz1 b);变为

JNIEXPORT jlong JNICALL Java_com_JDefine_func1(JEnv *, jobject , jint ,jobject );

3. JNI封装
我们接下来要做的就是将com_JDefine.h头文件中的函数进行实现。
新建一个C++项目,并把com_JDefine.h、jni.h、jni_md.h添加到当前目录下,或者新建一个include文件夹。
创建一个cpp工程:jni_test.cpp,对函数进行实现。如java中函数func1要实现C++中的函数GetID(int a ,ST1 st).则必须通过jni中的函数

JNIEXPORT jlong JNICALL Java_com_JDefine_func1(JEnv *, jobject , jint ,jobject );

实现,我们只需将C++中的函数GetID()放在Java_com_JDefine_func1()函数下就行了,而现在的难点就是如何将Java_com_JDefine_func1()的参数传入到GetID()中,和返回值为结构体或者指针的时候该如何处理。
对于一些如jint基本类型,我们可以直接转,如

jint a = 10;
int b = a;

但是类似于jstring/jArray/jobject等类型的就需要经过转换,才能传递到C++接口参数中。一般转换的步骤比较固定,但是由于类型之间可以相互组合,导致可能的情况很多,比如结构体中嵌套结构体(为jobject(jobject)型),字符串数组(为jArray(jstring)型)等,而且参数传递路径又可以分为

jni--->C++      C++----->jni

3.1 jni——>C++

  • jstring 转 const char * :
jstring str = "fsdfasdf";
const char *c = (char *)pEnv->GetStringUTFChars(str, 0);
  • jfloatArray转float[]
方法一:
//需求float[] unknown;
//joClazz1为类clazz1的实例,jfA为成员a的jfieldID
jfloatArray arrFloat = (jfloatArray)pEnv->GetObjectField(joClazz1,jfA);
jsize size = pEnv->GetArrayLength(arrFloat );
jfloat *pFloat = pEnv->GetFloatArrayElements(arrFloat,NULL);
for(int i = 0 ; i < size ; ++i)
{
....
}
pEnv->ReleaseFloatArrayElements(arrFloat ,pFloat ,0);

方法二:
jfloatArray arrFloat = (jfloatArray)pEnv->GetObjectField(joClazz1,jfA);
jsize size = pEnv->GetArrayLength(arrFloat );
jfloat ref[size ];
pEnv->GetFloatArrayRegion(arrFloat ,0,size,ref);
  • jobject

a . 获得jobject类型

//方法一:已知jobject obj
jclass jcClazz1 = pEnv->GetObjectClass(obj);
//方法二:
jclass jcClazz1 = pEnv->FindClass("com/JDefine$clazz1");

jclass为类类型,而jobject可以理解为jclass的实例,方法一的提前是已知一个实例obj,从而可以得到这个实例的类型。而方法二的查找路径和格式需要注意:”包名/类名$嵌套类”。

b.获得每个成员的jfieldID
格式如:

jfieldID jfValid = pEnv->GetFieldID(jcObjInfo,"nValid","I");

函数GetFieldID中第三个参数为成员nValid的类型的签名,其他类型的签名如下:

int    ----> I
long   ----> J
bool   ----> Z
string ----> Ljava/lang/String;
object ----> L包名/类名$嵌套类名;
method ----> (arg1 , arg2, ......)返回值签名,如(int,float)I
数组    ----> [类型签名,如[I

例子:
在JDefine.java文件中:

class clazz3
{
    clazz1[] a;
    clazz2 b;
    int[] c;
    string d;
};

令obj为clazz3的实例。
则:

//注意以下参数格式"Lcom/JDefine$clazz2;"FindClass函数的"com/JDefine$clazz1"的区别。
jclass jcClass3 = pEnv->GetObjectClass(obj);
jfieldID jfa = pEnv->GetFieldID(jcClass3 ,"a","[Lcom/JDefine$clazz1;");
jfieldID jfb = pEnv->GetFieldID(jcClass3 ,"b","Lcom/JDefine$clazz2;");
jfieldID jfc = pEnv->GetFieldID(jcClass3 ,"c","[I");
jfieldID jfd = pEnv->GetFieldID(jcClass3 ,"d","Ljava/lang/String;");

c. 获得每个成员的值

jobjectArray joaArray = (jobjectArray)pEnv->GetObjectField(obj,jfa);
jobject job = pEnv->GetObjectField(obj,jfb);
jintArray cArray = (jintArray)pEnv->GetObjectField(obj,jfc);
jstring d = (jstring)pEnv->GetObjectField(obj,jfd);

对于对象数组joaArray和整型数组cArray ,我们可以通过GetObjectArrayElement()和GetIntArrayElements()函数来获得某一个对象和cArray的首地址:

//获得joaArray 的大小:
jsize size = pEnv->GetArrayLength(joaArray);
for(int i = 0; i < size; ++i)
{
    //提取第i个对象
    jobject joa = pEnv->GetObjectArrayElement(joaArray,i);
    ……
    ……
}
//获得cArray 的大小:
jfloat *pInt = pEnv->GetIntArrayElements(cArray,NULL);
jsize size = pEnv->GetArrayLength(cArray);
......

//释放内存
pEnv->ReleaseIntArrayElements(cArray,pInt ,0);

注意:最后需要释放内存,否则容易内存泄露。但如果还有别的指针指向这块内存,就不能释放。

3.2 C++ ——> jni

  • char* 转 jstring
char *c = "zsdjfkdsf";
jstring str = pEnv->NewStringUTF(c)

当jstring在jobject中时:

//已知char *c = "zsdjfkdsf";
//假设对象jobject jo1中有成员jstring str,将str赋值c;
jclass jcStr = pEnv->GetObjectClass(jo1);
jfieldID jfStr = pEnv->GetFieldID(jcLogIn,"str","Ljava/lang/String;");
jstring myStr = pEnv->NewStringUTF(c);
pEnv->SetObjectField(jo1,jfStr ,myStr );
  • float *转 jfloatArray
方法一:
//已知float *pKnown ,求jfloatArray jUnknown;
//arrLen 为float数组长度
jfloatArray jUnknown= (jfloatArray)pEnv->NewFloatArray(arrLen);
jfloat *pData = pEnv->GetFloatArrayElements(jUnknown,NULL);
memcpy(pData ,pKnown,arrLen);

方法二:
pEnv->SetFloatArrayRegion(jUnknown,0,arrLen,pKnown);

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值