Java和c/c++之间的结构体传输

JNI提供了Java和native代码相互调用的接口,注意是相互调用,不仅仅是Java可以调用native,native也是可以调用Java的。但是使用的时候,我们会遇到一些问题,本文介绍一下Java对象和底层结构体的转换。

Java 对象

我们有Person类,

public class Person {
    public int ID;
    public String name;
    public byte[] data;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

底层结构体

Student结构体,由于我们底层采用c实现,而c没有字符串类型(C++有),所以我们采用char数组来存储字符串。

typedef struct {
    int ID;
    char name[255];
    char data[255];

} Student;
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

我们来介绍,如何将Student对象传递到底层,并将其转换为底层Student结构体。

NDK配置

首先,我们新建一个JNIUtils类,用来从java调用底层代码,

public class JNIUtils {
    public static native void passJava2Native(Student persion);
    public static native Student getJavaFromNative();
    static {
        System.loadLibrary("java2struct");
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

我们定义了两个本地方法,一个是将Java对象传递给底层,另一个是从底层返回一个Java对象。 
然后,我们执行javah -jni com.example.java2struct.JNIUtils命令来得到头文件,并得到两个本地方法的函数名,

JNIEXPORT void JNICALL Java_com_example_java2struct_JNIUtils_passJava2Native
  (JNIEnv *, jclass, jobject);
JNIEXPORT jobject JNICALL Java_com_example_java2struct_JNIUtils_getJavaFromNative
  (JNIEnv *, jclass);
 
 
  • 1
  • 2
  • 3
  • 4

底层接收Java对象

JNIEXPORT void JNICALL Java_com_example_java2struct_JNIUtils_passJava2Native(JNIEnv * env, jclass class, jobject object) 
 
 
  • 1

第三个参数就是我们传递进来的Student对象了。问题,就是,我们如何得到其各个成员变量的值呢? 
别急,JNI提供了GetFieldID方法,用来得到Java对象的FiledID,调用格式如下(C代码格式,C++代码有所不同),

(*env)->GetFieldID(env, clzz, fieldName, fieldSig);
 
 
  • 1

clzz为jclass类,可以通过(*env)->FindClass来获得,fieldName是成员的名称,fieldSig是成员的签名。关于类型签名,可以参考JNI java 类型签名 
得到ID后,我们根据成员的类型来决定调用以下方法,值得注意的是,Java的String也是类,所以可以通过GetObjectField来得到String类型的成员变量

GetObjectField, 
GetBooleanField, 
GetByteField, 
GetCharField, 
GetShortField, 
GetIntField, 
GetLongField, 
GetFloatField, 
GetDoubleField,

完整的代码如下,得到所有的成员变量后,我们将其值赋给结构体。

JNIEXPORT void JNICALL Java_com_example_java2struct_JNIUtils_passJava2Native(
        JNIEnv * env, jclass class, jobject object) {
    LOGE("call passJava2Native!");
    jclass jcRec = (*env)->FindClass(env, "com/example/java2struct/Student");
    jfieldID jfID = (*env)->GetFieldID(env, jcRec, "ID", "I");
    jfieldID jfname = (*env)->GetFieldID(env, jcRec, "name",
            "Ljava/lang/String;");
    jfieldID jfdata = (*env)->GetFieldID(env, jcRec, "data", "[B");
    int ID = (*env)->GetIntField(env, object, jfID);
    LOGE("ID: %d", ID);
    jstring name = (jstring)(*env)->GetObjectField(env, object, jfname);
    //convert jstring to char
    char* charname = (char*) (*env)->GetStringUTFChars(env, name, 0);
    LOGD("name: %s", charname);
    LOGE("name size : %d", strlen(charname));
    jbyteArray ja = (jbyteArray)(*env)->GetObjectField(env, object, jfdata);
    int nArrLen = (*env)->GetArrayLength(env, ja);
    char *chardata = (char*) (*env)->GetByteArrayElements(env, ja, 0);
    LOGW("data: %s", chardata);
    Student student;
    student.ID = ID;
    strcpy(student.name, charname);
    strcpy(student.data, chardata);
    printStudent(student);
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

我们就可以得到相应的成员变量啦。然后我们就可以赋值给结构体。

底层如何返回Java对象

上面介绍了底层如何解析Java对象,这部分介绍底层如何返回一个Java对象,步骤如下, 
1. (*env)->FindClass得到类 
2. (*env)->AllocObject新建一个类的示例 
3. (*env)->GetFieldID得到域ID 
4. 设置域,方法如下,

SetObjectField, 
SetBooleanField, 
SetByteField, 
SetCharField, 
SetShortField, 
SetIntField, 
SetLongField, 
SetFloatField, 
SetDoubleField,

设置域(成员变量)后,然后将该对象返回即可。完整的代码如下,

JNIEXPORT jobject JNICALL Java_com_example_java2struct_JNIUtils_getJavaFromNative(
        JNIEnv * env, jclass class) {
    Student student;
    student.ID = 1234;
    char *charname = "Paul";
    char *chardata = "I am a student";
    strcpy(student.name, charname);
    strcpy(student.data, chardata);
    printStudent(student);

    LOGE("call getJavaFromNative!");
    jclass jcRec = (*env)->FindClass(env, "com/example/java2struct/Student");
    jfieldID jfID = (*env)->GetFieldID(env, jcRec, "ID", "I");
    jfieldID jfname = (*env)->GetFieldID(env, jcRec, "name",
            "Ljava/lang/String;");
    jfieldID jfdata = (*env)->GetFieldID(env, jcRec, "data", "[B");

    jobject joRec = (*env)->AllocObject(env, jcRec);

    (*env)->SetIntField(env, joRec, jfID, student.ID);

    //convert char to jstring
    jstring jstrn = (*env)->NewStringUTF(env, student.name);
    (*env)->SetObjectField(env, joRec, jfname, jstrn);

    //convert char to byte
    int length = strlen(student.data);
    jbyteArray jbarr = (*env)->NewByteArray(env, length);
    jbyte *jb = (*env)->GetByteArrayElements(env, jbarr, 0);
    memcpy(jb, student.data, length);
    (*env)->SetByteArrayRegion(env, jbarr, 0, length, jb);
    (*env)->SetObjectField(env, joRec, jfdata, jbarr);
    return joRec;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

代码下载地址Github

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: Ubuntu是一种很流行的Linux操作系统,可以与C语言一起使用来进行串口通信。串口通信是通过串口传输数据的过程,可用于通信设备之间的数据传输。在Ubuntu中与串口通信可能需要进行以下几个步骤: 1. 安装串口驱动程序:首先需要确定计算机与哪个串口进行通信,然后安装相应的串口驱动程序。 2. 打开串口:可以使用C语言编写程序,打开串口进行通信。使用open()函数打开串口,设置串口参数。 3. 发送和接收数据:可以使用write()函数向串口发送数据,使用read()函数从串口接收数据。注意发送和接收的数据类型需要匹配。 4. 关闭串口:在通信结束后使用close()函数关闭串口。 总之,使用Ubuntu与C语言进行串口通信可以实现设备之间的数据传输,但需要注意串口的配置、调试和错误处理,以确保通信的正确性和可靠性。同时,还可以考虑使用其他编程语言或通信协议来实现更高效的通信方案。 ### 回答2: Ubuntu是一种基于Linux操作系统的开源软件,它支持C语言,可以通过该语言实现串口通信。串口通信是指通过串口进行数据传输和通信,通常用于连接计算机和一些外围设备,如感器、控制器和仪器等。在Ubuntu上进行串口通信需要安装相应的串口通信库,常用的有wiringPi和libserial等。这些库提供了函数和方法来操作串口,如打开、关闭串口、发送和接收数据等操作。 在C语言中,可以使用系统调用(open、read、write、close)来实现对串口的操作。首先,需要打开串口并设置串口参数,如波特率、数据位、校验位、停止位等。然后,可以使用write函数向串口发送数据,使用read函数从串口读取数据。最后,完成操作后需要关闭串口。在传输过程中,需要注意数据的格式和传输速率,以确保成功传输和解析数据。 除了C语言外,Ubuntu还支持其他编程语言来进行串口通信,如Python、Java等。这些语言也可以通过相应的库来实现串口通信,例如Python的pyserial库和Java的RXTX库。在使用这些语言进行串口通信时,同样需要了解串口的基本参数和使用方法。 在实际应用中,串口通信可以用于许多场景,如数据采集、控制和通信等。通过Ubuntu上的C语言或其他编程语言,我们可以轻松地实现串口通信,从而满足不同应用场景的需求。 ### 回答3: Ubuntu是一个开源的操作系统,适用于各种计算机和设备。串口通信也称为串行通信,是一种基于串行端口(COM端口)的通信方式,它在计算机和设备之间传输数据。本文将讨论如何在Ubuntu中使用C编程语言实现串口通信。 Ubuntu提供了读写串口的API函数,我们可以用C语言编写基于这些函数的程序来实现串口通信。以下是基本的步骤: 1. 打开串行端口 使用"open"函数来打开串行端口,该函数返回一个文件描述符,以用于后续的读写操作。例如: int fd; // 串口文件描述符 fd = open("/dev/ttyACM0", O_RDWR | O_NOCTTY | O_NDELAY); if(fd == -1){ // 打开串口失败 } 2. 配置串口 在打开串口之后,需要使用"tcgetattr"和"tcsetattr"函数来配置串口的参数,例如波特率、数据位数、校验位等。以下是一个示例程序: struct termios options; // 串口参数结构体 tcgetattr(fd, &options); // 获取当前串口参数 cfsetispeed(&options, B9600); // 设置输入速度为9600bps cfsetospeed(&options, B9600); // 设置输出速度为9600bps options.c_cflag |= (CLOCAL | CREAD); // 本地连接和接受使能 options.c_cflag &= ~PARENB; // 不使用校验位 options.c_cflag &= ~CSTOPB; // 数据位为1个停止位 options.c_cflag &= ~CSIZE; // 清除数据位掩码 options.c_cflag |= CS8; // 数据位为8个位 tcsetattr(fd, TCSANOW, &options); // 设置新的串口参数 3. 读取和写入数据 使用"read"和"write"函数来分别读取和写入串口数据,例如: char buf[64]; int len; len = read(fd, buf, sizeof(buf)); // 读取串口数据 if(len == -1){ // 读取失败 } write(fd, "Hello World!", 12); // 写入串口数据 4. 关闭串口 使用"close"函数来关闭串口: close(fd); 总之,通过使用C语言和Ubuntu提供的串口API函数,我们可以轻松地实现串口通信。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值