Android JNI/NDK入门指南目录
JNI/NDK入门指南之正确姿势了解JNI和NDK
JNI/NDK入门指南之JavaVM和JNIEnv
JNI/NDK入门指南之JNI数据类型,描述符详解
JNI/NDK入门指南之jobject和jclass
JNI/NDK入门指南之javah和javap的使用和集成
JNI/NDK入门指南之Eclipse集成NDK开发环境并使用
JNI/NDK入门指南之JNI动/静态注册全分析
JNI/NDK入门指南之JNI字符串处理
JNI/NDK入门指南之JNI访问数组
JNI/NDK入门指南之C/C++通过JNI访问Java实例属性和类静态属性
JNI/NDK入门指南之C/C++通过JNI访问Java实例方法和类静态方法
JNI/NDK入门指南之JNI异常处理
JNI/NDK入门指南之JNI多线程回调Java方法
JNI/NDK入门指南之正确姿势了解,使用,管理,缓存JNI引用
JNI/NDK入门指南之调用Java构造方法和父类实例方法
JNI/NDK入门指南之C/C++结构体和Java对象转换方式一
JNI/NDK入门指南之C/C++结构体和Java对象转换方式二
在Jni的开发过程中,会经常遇到有将C/C++结构体和Java对象之间相互转换的一些需求。那么接下来,在本文的篇章中我将会讲解到其中的一种,并且这两种方式在实际的开发工作中也有经常用到。下面正式开始我们的本篇介绍之旅,方式一介绍。
第一种方法使用的是ByteBuffer作为桥接来实现C/C++结构体和Java对象之间通过Jni相互转换的功能。其原理如下,通过ByteBuffer将Java对象转换成byte数组数据,然后通过Jni传递到Native C/C++层,对byte数据数据进行相关操作和赋值,操作结束以后然后再次通过ByteBuffer从byte数组中读取出相关的值。
ByteBuffer 定义
在NIO中,数据的读写操作始终是与缓冲区相关联的(读取时信道(SocketChannel)将数据读入缓冲区,写入时首先要将发送的数据按顺序填入缓冲区)缓冲区是定长的,基本上它只是一个列表,它的所有元素都是基本数据类型。ByteBuffer是最常用的缓冲区,它提供了读写其他数据类型的方法,且信道的读写方法只接收ByteBuffer。
在前面的章节已经介绍了,具体使用什么方法。那么在本章节里面将介绍具体的步骤,那么,下面来开始我们的介绍。
本篇的标题是C/c++结构体和Java对象之间的转换,那么下面介绍我们要相互转换的数,这里只是为了做演示使用,至于数据还可以进行扩展,或者学完此种方法以后读者也可以亲自操作实践一把。
(1) C/C++结构体
//Must be byte aligned
typedef struct{
unsigned char mArrayChar[4];
unsigned short mShort;
unsigned char mChar;
}ClassA_;
(2) Java类
package com.xxx.object2struct;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
public class ClassA {
byte[] byteAarray = new byte[4];
short mShort;
byte mByte;
public ClassA() {
this.mByte = 10;
this.mShort = 20;
for (int i = 0; i < byteAarray.length; i++) {
byteAarray[i] = (byte) (i * 100);
}
}
/**
* Converting object into byte array.
*
* @return byte array of ClassA
*/
public byte[] serialToBuffer() {
ByteBuffer mByteBuffer = ByteBuffer.allocate(1024);//创建ByteBuffer
mByteBuffer.clear();
mByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
mByteBuffer.put(byteAarray);
mByteBuffer.putShort((short) mShort);
mByteBuffer.put(mByte);
mByteBuffer.flip();//回绕缓冲区
byte[] ret = new byte[mByteBuffer.limit()];//获取缓冲区的当前终点
mByteBuffer.get(ret);
return ret;
}
/**
* Converting byte array into object.
*
* @param bb
* byte array of ClassA
*/
public void serialFromBuffer(byte[] bb) {
ByteBuffer mByteBuffer = ByteBuffer.wrap(bb);//使用数组创建
mByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
mByteBuffer.get(this.byteAarray);
this.mShort = (short) mByteBuffer.getShort();
this.mByte = mByteBuffer.get();
}
@Override
public String toString() {
return "ClassA [byteAarray=" + Arrays.toString(byteAarray) + ", mByte="
+ mByte + ", mShort=" + mShort + "]";
}
}
前面的章节介绍了,需要转换的数据,那么在这个章节将要介绍具体的步骤。
public class JniUtils {
public static native int getClassA(byte[] data);
static{
System.loadLibrary("jni_struct2class");
}
}
这个比较简单,就不加以赘述了,因为确实代码量比较少太简单,我想讲解也没有啥好讲解的。
ClassA mClassA = new ClassA();
//借助ByteBuffer将Java对象转换成byte数据
byte[] data = mClassA.serialToBuffer();
//借助Jni在Native层对byte数据赋值
JniUtils.getClassA(data);
//读取赋值以后byte数组里面的数据
mClassA.serialFromBuffer(data);
这都是一些比较常规的操作,主要是动态注册native方法,然后通过指针强制转换将Java层传递下来的byte数据强制转换成C/C++结构体指针,然后进行相关的赋值操作。
#include"common.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define TAG "JniUtils"
#define LOGE(TAG,...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)
static const char* const kClassPathName = "com/xxx/object2struct/JniUtils";
//dump info
static void dumpCalssAInfo(ClassA_ *classA){
LOGE(TAG, "+++ Start dumpClassAInfo +++\n");
LOGE(TAG, "+++ mArrayChar[0] : %d +++\n", classA->mArrayChar[0]);
LOGE(TAG, "+++ mArrayChar[1] : %d +++\n", classA->mArrayChar[1]);
LOGE(TAG, "+++ mArrayChar[2] : %d +++\n", classA->mArrayChar[2]);
LOGE(TAG, "+++ mArrayChar[3] : %d +++\n", classA->mArrayChar[3]);
LOGE(TAG, "+++ mChar : %d +++\n", classA->mChar);
LOGE(TAG, "+++ mShort : %d +++\n", classA->mShort);
LOGE(TAG, "+++ End dumpClassAInfo +++\n");
}
static int getClassAInfo(ClassA_ * classA){
classA->mArrayChar[0] = 0;
classA->mArrayChar[1] = 1;
classA->mArrayChar[2] = 2;
classA->mArrayChar[3] = 3;
classA->mChar = 4;
classA->mShort = 100;
return 0;
}
/*
* Class: com_xxx_object2struct_JniUtils
* Method: getClassA
* Signature: ([B)I
*/
JNIEXPORT jint JNICALL Java_com_xxx_object2struct_JniUtils_getClassA
(JNIEnv * env, jobject obj, jbyteArray byteArray)
{
LOGE(TAG,"Java_com_xxx_object2struct_JniUtils_getClassA");
if(byteArray == NULL)
return -1;
jbyte *p_ClassA = NULL;
p_ClassA = env->GetByteArrayElements(byteArray, 0);
dumpCalssAInfo((ClassA_ *)p_ClassA)
getClassAInfo((ClassA_ *)p_ClassA);//这里将byteArray强制转换成结构体指针
dumpCalssAInfo((ClassA_ *)p_ClassA);
env->ReleaseByteArrayElements(byteArray, p_ClassA, 0);
return 0;
}
// Dalvik VM type signatures
static JNINativeMethod gMethods[] = {
{
"getClassA", "([B)I",(void*) Java_com_xxx_object2struct_JniUtils_getClassA},
};
//动态注册函数
int register_native_interface(JNIEnv *env){
jclass clazz;
clazz = env->FindClass(kClassPathName);
if (clazz == NULL)
return JNI_FALSE;
if (env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0])) < 0){
return JNI_FALSE;
}
return JNI_TRUE;
}
jint JNI_OnLoad(JavaVM * vm, void * reserved){
JNIEnv * env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
LOGE(TAG, "ERROR: GetEnv failed\n");
goto bail;
}
assert(env != NULL);
if (register_native_interface(env) < 0) {
LOGE(TAG,"ERROR: native registration failed\n");
goto bail;
}
result = JNI_VERSION_1_4;
bail:
return result;
}
void JNI_OnUnload(JavaVM* vm, void* reserved){
LOGE(TAG, "JNI_OnUnload called\n");
}
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS :=optional
LOCAL_C_INCLUDES := $(KERNEL_HEADERS)
LOCAL_SHARED_LIBRARIES := libcutils liblog libutils libicuuc
LOCAL_LDLIBS := -lm -llog
LOCAL_MODULE:= libjni_struct2class
LOCAL_SRC_FILES:= com_xxx_object2struct_JniUtils.cpp
LOCAL_PRELINK_MODULE := false
include $(BUILD_SHARED_LIBRARY)
在前面的章节里面,我们将原理和步骤都已经交代和讲解OK了,那么到了最终的步骤了就是见证奇迹的时刻到了,看下我们的效果。是不是已经OK了
λ adb logcat -s JniUtils
--------- beginning of main
--------- beginning of system
12-02 15:27:10.916 27156 27156 E JniUtils: The ClassA : ClassA [byteAarray=[0, 10, 20, 30], mByte=10, mShort=20]
12-02 15:27:10.919 27156 27156 I JniUtils: Java_com_xxx_object2struct_JniUtils_getClassA
12-02 15:27:10.919 27156 27156 I JniUtils: +++ Start dumpClassAInfo +++
12-02 15:27:10.919 27156 27156 I JniUtils: +++ mArrayChar[0] : 0 +++
12-02 15:27:10.919 27156 27156 I JniUtils: +++ mArrayChar[1] : 10 +++
12-02 15:27:10.919 27156 27156 I JniUtils: +++ mArrayChar[2] : 20 +++
12-02 15:27:10.919 27156 27156 I JniUtils: +++ mArrayChar[3] : 30 +++
12-02 15:27:10.919 27156 27156 I JniUtils: +++ mChar : 10 +++
12-02 15:27:10.919 27156 27156 I JniUtils: +++ mShort : 20 +++
12-02 15:27:10.919 27156 27156 I JniUtils: +++ End dumpClassAInfo +++
12-02 15:27:10.919 27156 27156 I JniUtils: +++ Start dumpClassAInfo +++
12-02 15:27:10.919 27156 27156 I JniUtils: +++ mArrayChar[0] : 0 +++
12-02 15:27:10.920 27156 27156 I JniUtils: +++ mArrayChar[1] : 1 +++
12-02 15:27:10.920 27156 27156 I JniUtils: +++ mArrayChar[2] : 2 +++
12-02 15:27:10.920 27156 27156 I JniUtils: +++ mArrayChar[3] : 3 +++
12-02 15:27:10.920 27156 27156 I JniUtils: +++ mChar : 4 +++
12-02 15:27:10.920 27156 27156 I JniUtils: +++ mShort : 100 +++
12-02 15:27:10.920 27156 27156 I JniUtils: +++ End dumpClassAInfo +++
12-02 15:27:10.920 27156 27156 E JniUtils: The ClassA : ClassA [byteAarray=[0, 1, 2, 3], mByte=4, mShort=100]
这种通过ByteBuffer作为桥接的方式来实现的转换,其优点是比较简单的而且讨巧的方式,但是当要转换的数据比较复杂譬如自定义对象或者复杂的数据时就不适用了。且在这种转换中一定要注意C/C++结构体一定要满足字节对齐法则,不然会出现一些你意想不到的结果。在下一篇章中将会介绍传统的转换方式,虽然复杂一定但是使用的范围更广。且听下回分解!青山不改绿水长流,各位江湖见!