Reputation: 146
I am successfully able to get this structure with a single byte array field as output as part of another bigger structure. However, when I pass this same structure as an input argument, the native code is unable to decipher the data in the byte array. I have verified that the native code is able to successfully handle the call on direct invocation without JNA in the picture. I need to know, what is the problem with the structure definition in Java which could be causing this problem.
Details I am using a third party library which has the following C structure for a GUID:
typedef struct GuidType
{
uint8_t data[16];
} GuidType;
GuidType gets used in other bigger structures and is a common identity information that is used to perform various kinds of data manipulation. For example,
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;
The third party library has APIs on the following lines:
int getAllDataObjectType1Instances(DataObjectType1Array ** dataObjectType1Instances);
int getDataObjectType2ForGuid(GuidType guid, DataObjectType2 ** dataObjectType2);
After reading the JNA documentation and using Jnaerator, I could come up with the following Java-JNA code:
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);
With the above code, I am able to successfully invoke the getAllDataObjectType1Instances API which returns all the DataObjectType1 instances and has all the fields correctly initialized in the structure. However, the getDataObjectType2ForGuid call always fails. The code is on the following lines:
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.
}
}
As mentioned above, the libraryInstance.getDataObjectType2ForGuid() call fails with an error that indicates that actual data is not getting passed to the native code. I have also tried creating a new GuidType instance with the right data in the byte array field and then passing it to getDataObjectType2ForGuid(), but with no success.
I am unable to identify what is going wrong here? Can someone help. Let me know, if more details are needed or something is not clear. Thanks in advance.
EDIT Not sure if it matters, but the native library is written in C++. So we are dealing with a C++ struct here.
Upvotes: 1
Views: 396
Reputation: 9131
I originally answered suggesting you needed to allocate memory for the structure pointed to by the PointerByReference
. That turned out to be the wrong suggestion, because, as you later indicated, the API told you it would allocate its own memory and return a pointer to it.
As you've observed, you hit an unusual situation where a C library expects a struct to be passed by value. The key clue is in this method signature:
int getDataObjectType2ForGuid(GuidType guid, PointerByReference dataObjectType2Instance);
JNA by default treats structures ByValue
when declared inside other structures (such as how it is defined in DataObjectType2
) and ByReference
when passed in method arguments (which usually have the *
indicating a pointer -- you'd have seen GuidType *guid
).
In the case where the native api differs from this default convention (either the pointer-based variant in a structure, or the full structure in a method call) you need to explicitly tag the structures ByValue
or ByReference
.
This is discussed in the JNA Overview under the "Structures" heading.
Upvotes: 1
Reputation: 146
Based on the other answer here, I could resolve the issue by passing GuidType.ByValue
to getDataObjectType2ForGuid
. Changes in the code are as follows:
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);
Thanks @Daniel Widdis for all the support.
Upvotes: 2