Search code examples
javaandroidcjava-native-interfacejna

Passing a pointer to a structure (StructureByReference) in to C code, How to access that data again?


I am passing the following Test Structure from Java in Android to a native C function in a shared library:

@Structure.FieldOrder({"testDouble", "testInt", "testPointer"})
public class Test extends Structure implements Structure.ByReference {

    public double testDouble;
    public int testInt;
    public Pointer testPointer;


    public Test(double testDouble, int testInt, Pointer testPointer) {
            this.testDouble = testDouble;
            this.testInt = testInt;
            this.testPointer = testPointer;

    }

    Getters & Setters...

The native C code handles the structure as:

    void Test(double extraDouble, Test *TestResult)

The Native C code takes the pointer to the TestResult and populates all the fields.

I then need to be able to access that data back in the Java code - i.e access the Test Object and read the data testDouble, testInt and the data at testPointer, which is a double[]. The TestResult object is not passed back by the function; as you can see, it is just overwritten in the memory I assume.

I understand how to send the data; however, when I try to access the object again by the value back in Java, after the population has supposed to have taken place, I just get zero data - as if it is a brand new object, not the original one with the now populated data.

How can I access the data that my C code should have populated? I.e. I think access the memory at that pointer again?

I assume in the Native C code it simply overwrites that memory block with the new data and then I have to find some way to be able to access that block of data back in java and rebuild my Test Structure. Please correct me if any of my presumptions are incorrect.

If you need any clarification please let me know.

The C struct is just:

typedef struct {
  double testDouble;
  int testInt;
  double testPointer[3];
} Test;

Solution

  • You are not properly passing the double array. Since the array is of a fixed size, you can simply map it as part of your Structure:

    FieldOrder({"testDouble", "testInt", "testPointer"})
    public class Test extends Structure {
    
        public double testDouble;
        public int testInt;
        public double[] testPointer = new double[3];
    
        // other code
    }
    

    This gives your structure a total size of 36 bytes (8 + 4 + 3x8). Currently with the Pointer you're only passing 8 + 4 + 8 = 20 bytes.

    This would easily explain why you're getting nothing for the double, as you've only asked for an 8-byte pointer but you need 24 bytes for the double array.

    It's not clear exactly why the double and int don't populate. Without the details of the Native API I'm not sure how it would know you haven't allocated enough memory for it, as my assumption would be it would try to write those first two fields, write the third field as the first double in the array, and then probably crash trying to write outside allocated memory for the remaining two doubles.

    You haven't shown how you are passing the function to the native side, either. You don't need to include the "ByReference" and, in fact, it could cause problems if you did use it, as you'd have to specifically use "getValue()" on the returned structure. But if you pass it like this (with the proper mapping) it should work:

    mapping:

    void Test(double extraDouble, Test testResult)
    

    call:

    Test testResult = new Test();
    Test(extraDouble, testResult)