无法通过 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 变体,或方法调用中的完整结构),您需要显式标记结构ByValueByReference.

这在 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 的所有支持。