无法通过 JNA 将具有单字节数组字段的结构传递给本机代码
Unable to pass a structure with single byte array field to native code via JNA
我成功地获得了这个结构,它有一个单字节数组字段作为输出,作为另一个更大结构的一部分。但是,当我将这个相同的结构作为输入参数传递时,本机代码无法破译字节数组中的数据。我已验证本机代码能够在图片中没有 JNA 的情况下成功处理直接调用的调用。我需要知道,Java 中的结构定义有什么问题可能会导致此问题。
详情
我正在使用具有以下 GUID C 结构的第三方库:
typedef struct GuidType
{
uint8_t data[16];
} GuidType;
GuidType 用于其他更大的结构,是一种用于执行各种数据操作的通用标识信息。例如,
typedef struct DataObjectType1 {
GuidType guid;
const char * detail1;
// various other fields
} DataObjectType1;
typedef struct DataObjectType1Array {
size_t size;
DataObjectType1 ** data; // array of pointers. Each pointer points to a structure
}
typedef struct DataObjectType2 {
GuidType guid;
const char * detail2;
// various other fields
} DataObjectType2;
第三方库在以下行中有 APIs:
int getAllDataObjectType1Instances(DataObjectType1Array ** dataObjectType1Instances);
int getDataObjectType2ForGuid(GuidType guid, DataObjectType2 ** dataObjectType2);
阅读 JNA 文档并使用 Jnaerator 后,我可以得出以下 Java-JNA 代码:
GuidType extends Structure {
public byte[] data = new byte[16];
public GuidType(Pointer p){
super(p);
read();
}
public GuidType() { }
}
DataObjectType1 extends Structure {
public GuidType guid;
public String detail1;
// various other fields
// Default constructor and constructor with pointer as argument
}
DataObjectType1Array extends Structure {
public int size;
public Pointer data;
// Default constructor and constructor with pointer as argument
}
DataObjectType2 extends Structure {
public GuidType guid;
public String detail2;
// various other fields
// Default constructor and constructor with pointer as argument
}
// Native API equivalent in Java using JNA
int getAllDataObjectType1Instances(PointerByReference dataObjectType1Instances);
int getDataObjectType2ForGuid(GuidType guid, PointerByReference dataObjectType2Instance);
使用上面的代码,我能够成功调用 getAllDataObjectType1Instances API,其中 returns 所有 DataObjectType1 实例并在结构中正确初始化了所有字段。但是,getDataObjectType2ForGuid 调用总是失败。代码在以下几行:
PointerByReference dataObjectType1Instances = new PointerByReference();
int result = libraryInstance.getAllDataObjectType1Instances(dataObjectType1Instances);
if(result == 0){
DataObjectType1Array dataObjectType1Array = new DataObjectType1Array(dataObjectType1Instances.getValue());
Pointer[] pointers = dataObjectType1Array.data.getPointerArray(0, dataObjectType1Array.size);
for(Pointer pointer : pointers){
DataObjectType1 dataObjectType1 = new DataObjectType1(pointer);
System.out.println(Arrays.toString(dataObjectType1.guid.data)); // this shows the correct GUID details
// More print statements that indicate other fields in dataObjectType1 are also initialized correctly.
PointerByReference dataObjectType2PointerByReference = new PointerByReference();
result = libraryInstance.getDataObjectType2ForGuid(dataObjectType1.guid, dataObjectType2PointerByReference); // this always fails. On success, library assigns the address of a DataObjectType2 pointer to dataObjectType2PointerByReference.
// ... Code to extract DataObjectType2 instance from dataObjectType2Reference.
}
}
如上所述,libraryInstance.getDataObjectType2ForGuid() 调用失败并出现错误,表明实际数据未传递到本机代码。我还尝试在字节数组字段中使用正确的数据创建一个新的 GuidType 实例,然后将其传递给 getDataObjectType2ForGuid(),但没有成功。
我无法确定这里出了什么问题?有人可以帮忙吗。让我知道,如果需要更多细节或不清楚的地方。提前致谢。
编辑 不确定这是否重要,但本机库是用 C++ 编写的。所以我们在这里处理 C++ 结构。
我最初的回答是建议您需要为 PointerByReference
指向的结构分配内存。结果证明这是错误的建议,因为正如您稍后指出的那样,API 告诉您它将分配自己的内存并 return 指向它的指针。
如您所见,您遇到了一种不寻常的情况,即 C 库期望按值传递结构。关键线索在此方法签名中:
int getDataObjectType2ForGuid(GuidType guid, PointerByReference dataObjectType2Instance);
JNA 默认处理结构 ByValue
在其他结构中声明时(例如它在 DataObjectType2
中的定义方式)和 ByReference
在传入方法参数时(通常有*
表示一个指针——你会看到 GuidType *guid
).
如果本机 api 不同于此默认约定(结构中的 pointer-based 变体,或方法调用中的完整结构),您需要显式标记结构ByValue
或 ByReference
.
这在 JNA Overview“结构”标题下进行了讨论。
根据另一个答案 ,我可以通过将 GuidType.ByValue
传递给 getDataObjectType2ForGuid
来解决问题。代码改动如下:
class GuidType extends Structure {
public static class ByValue extends GuidType implements Structure.ByValue {
public ByValue() { }
public ByValue(Pointer p){ super(p); read(); }
}
// ... other existing code
}
// Change in signature of getDataObjectType2ForGuid to use GuideType.ByValue
int getDataObjectType2ForGuid(GuidType.ByValue guid, DataObjectType2 ** dataObjectType2);
// Code to invoke getDataObjectType2ForGuid()
result = libraryInstance.getDataObjectType2ForGuid(
new GuidType.ByValue(dataObjectType1.guid.getPointer()),
dataObjectType2PointerByReference);
感谢@Daniel Widdis 的所有支持。
我成功地获得了这个结构,它有一个单字节数组字段作为输出,作为另一个更大结构的一部分。但是,当我将这个相同的结构作为输入参数传递时,本机代码无法破译字节数组中的数据。我已验证本机代码能够在图片中没有 JNA 的情况下成功处理直接调用的调用。我需要知道,Java 中的结构定义有什么问题可能会导致此问题。
详情 我正在使用具有以下 GUID C 结构的第三方库:
typedef struct GuidType
{
uint8_t data[16];
} GuidType;
GuidType 用于其他更大的结构,是一种用于执行各种数据操作的通用标识信息。例如,
typedef struct DataObjectType1 {
GuidType guid;
const char * detail1;
// various other fields
} DataObjectType1;
typedef struct DataObjectType1Array {
size_t size;
DataObjectType1 ** data; // array of pointers. Each pointer points to a structure
}
typedef struct DataObjectType2 {
GuidType guid;
const char * detail2;
// various other fields
} DataObjectType2;
第三方库在以下行中有 APIs:
int getAllDataObjectType1Instances(DataObjectType1Array ** dataObjectType1Instances);
int getDataObjectType2ForGuid(GuidType guid, DataObjectType2 ** dataObjectType2);
阅读 JNA 文档并使用 Jnaerator 后,我可以得出以下 Java-JNA 代码:
GuidType extends Structure {
public byte[] data = new byte[16];
public GuidType(Pointer p){
super(p);
read();
}
public GuidType() { }
}
DataObjectType1 extends Structure {
public GuidType guid;
public String detail1;
// various other fields
// Default constructor and constructor with pointer as argument
}
DataObjectType1Array extends Structure {
public int size;
public Pointer data;
// Default constructor and constructor with pointer as argument
}
DataObjectType2 extends Structure {
public GuidType guid;
public String detail2;
// various other fields
// Default constructor and constructor with pointer as argument
}
// Native API equivalent in Java using JNA
int getAllDataObjectType1Instances(PointerByReference dataObjectType1Instances);
int getDataObjectType2ForGuid(GuidType guid, PointerByReference dataObjectType2Instance);
使用上面的代码,我能够成功调用 getAllDataObjectType1Instances API,其中 returns 所有 DataObjectType1 实例并在结构中正确初始化了所有字段。但是,getDataObjectType2ForGuid 调用总是失败。代码在以下几行:
PointerByReference dataObjectType1Instances = new PointerByReference();
int result = libraryInstance.getAllDataObjectType1Instances(dataObjectType1Instances);
if(result == 0){
DataObjectType1Array dataObjectType1Array = new DataObjectType1Array(dataObjectType1Instances.getValue());
Pointer[] pointers = dataObjectType1Array.data.getPointerArray(0, dataObjectType1Array.size);
for(Pointer pointer : pointers){
DataObjectType1 dataObjectType1 = new DataObjectType1(pointer);
System.out.println(Arrays.toString(dataObjectType1.guid.data)); // this shows the correct GUID details
// More print statements that indicate other fields in dataObjectType1 are also initialized correctly.
PointerByReference dataObjectType2PointerByReference = new PointerByReference();
result = libraryInstance.getDataObjectType2ForGuid(dataObjectType1.guid, dataObjectType2PointerByReference); // this always fails. On success, library assigns the address of a DataObjectType2 pointer to dataObjectType2PointerByReference.
// ... Code to extract DataObjectType2 instance from dataObjectType2Reference.
}
}
如上所述,libraryInstance.getDataObjectType2ForGuid() 调用失败并出现错误,表明实际数据未传递到本机代码。我还尝试在字节数组字段中使用正确的数据创建一个新的 GuidType 实例,然后将其传递给 getDataObjectType2ForGuid(),但没有成功。
我无法确定这里出了什么问题?有人可以帮忙吗。让我知道,如果需要更多细节或不清楚的地方。提前致谢。
编辑 不确定这是否重要,但本机库是用 C++ 编写的。所以我们在这里处理 C++ 结构。
我最初的回答是建议您需要为 PointerByReference
指向的结构分配内存。结果证明这是错误的建议,因为正如您稍后指出的那样,API 告诉您它将分配自己的内存并 return 指向它的指针。
如您所见,您遇到了一种不寻常的情况,即 C 库期望按值传递结构。关键线索在此方法签名中:
int getDataObjectType2ForGuid(GuidType guid, PointerByReference dataObjectType2Instance);
JNA 默认处理结构 ByValue
在其他结构中声明时(例如它在 DataObjectType2
中的定义方式)和 ByReference
在传入方法参数时(通常有*
表示一个指针——你会看到 GuidType *guid
).
如果本机 api 不同于此默认约定(结构中的 pointer-based 变体,或方法调用中的完整结构),您需要显式标记结构ByValue
或 ByReference
.
这在 JNA Overview“结构”标题下进行了讨论。
根据另一个答案 GuidType.ByValue
传递给 getDataObjectType2ForGuid
来解决问题。代码改动如下:
class GuidType extends Structure {
public static class ByValue extends GuidType implements Structure.ByValue {
public ByValue() { }
public ByValue(Pointer p){ super(p); read(); }
}
// ... other existing code
}
// Change in signature of getDataObjectType2ForGuid to use GuideType.ByValue
int getDataObjectType2ForGuid(GuidType.ByValue guid, DataObjectType2 ** dataObjectType2);
// Code to invoke getDataObjectType2ForGuid()
result = libraryInstance.getDataObjectType2ForGuid(
new GuidType.ByValue(dataObjectType1.guid.getPointer()),
dataObjectType2PointerByReference);
感谢@Daniel Widdis 的所有支持。