javac++jna

Access a C++ struct with two vectors from Java using JNA


This follows on from my earlier question. I now need to access a C++ struct that has multiple members of type std::vector<double> or std::vector<int>. However while I seem to successfully access the first member, the second produces garbage. Code below:

my_struct.cpp:

#include <iostream>
#include <memory>

#include <vector>

struct MyStruct {
    int intVectorSize;
    int doubleVectorSize;
    std::vector<int> intVector;
    std::vector<double> doubleVector;
};


extern "C" {

    MyStruct* create_my_struct() {
        MyStruct* myStruct = new MyStruct();

        myStruct->intVectorSize = 2;
        myStruct->doubleVectorSize = 2;
        myStruct->intVector.push_back(5);
        myStruct->intVector.push_back(6);
        myStruct->doubleVector.push_back(1.1);
        myStruct->doubleVector.push_back(2.3);

        return myStruct;
    }

    void delete_my_struct(MyStruct* myStruct) {
        delete myStruct;
    }
}

BasicStruct.java:

import com.sun.jna.Structure;
import com.sun.jna.Native;
import com.sun.jna.Pointer;

import java.util.Arrays;

import com.sun.jna.Library;
import com.sun.jna.Structure.FieldOrder;

public class BasicStruct {
    public interface MyCppLibrary extends Library {
        MyCppLibrary INSTANCE = (MyCppLibrary) Native.load("mycpp", MyCppLibrary.class);

        @FieldOrder({"intVectorSize", "doubleVectorSize", "intVector", "doubleVector"})
        public class MyStruct extends Structure {
            public int intVectorSize;
            public int doubleVectorSize;

            public Pointer intVector;
            public Pointer doubleVector;
        }

        MyStruct create_my_struct();
        void delete_my_struct(MyStruct struct);

    }


    public static void main(String[] args) {
        MyCppLibrary lib = MyCppLibrary.INSTANCE;

        MyCppLibrary.MyStruct myStruct = lib.create_my_struct();

        System.out.println("intVectorSize: " + myStruct.intVectorSize);
        System.out.println("doubleVectorSize: " + myStruct.doubleVectorSize);
        System.out.println("intVector: " + myStruct.intVector);
        System.out.println("doubleVector: " + myStruct.doubleVector);


        int[] intArray = myStruct.intVector.getIntArray(0, myStruct.intVectorSize);
        double[] doubleArray = myStruct.doubleVector.getDoubleArray(0, myStruct.doubleVectorSize);

        System.out.println("intVector contents: " + Arrays.toString(intArray));
        System.out.println("doubleVector contents: " + Arrays.toString(doubleArray));

        lib.delete_my_struct(myStruct);

    }
}

Compiling the cpp code using g++ in WSL2 running Ubuntu 20.04 into a shared lib whose name matches that to be loaded from the Java code:

g++ -v -shared -fPIC -o libmycpp.so my_struct.cpp

And compiling and executing the Java code (ensuring that the JNA jar file is on the classpath):

 $ javac -classpath ./jars/jna-5.15.0.jar  BasicStruct.java
 $ java  -classpath ./jars/jna-5.15.0.jar:. BasicStruct

I get the following results:

intVectorSize: 2
doubleVectorSize: 2
intVector: native@0x7f0ea8294600
doubleVector: native@0x7f0ea8294608
intVector contents: [5, 6]
doubleVector contents: [6.9469400861921E-310, 0.0]

Note that while the output for the intVector is correct, the output for the doubleVector attribute shown is wrong.

I tried other combinations of ordering the attributes and notice after the first vector is read, the subsequent attributes appear corrupted.

Any suggestions on how to fix this? Thanks in advance.


Solution

  • It seems that JNA needs to access the raw array and not the std::vector itself in order to convert it into a java array (see this for getIntArray and getDoubleArray). It means that your C structure has to provide the result of std::vector::data.

    So the C library would be for instance:

    #include <vector>
    #include <cstdint>
    
    struct MyStruct 
    {
        int intVectorSize;
        int doubleVectorSize;
        
        const int32_t*  intVectorPtr;
        const double*   doubleVectorPtr;
    
        std::vector<int32_t> intVector;
        std::vector<double>  doubleVector;
    };
    
    extern "C" 
    {
        MyStruct* create_my_struct() 
        {
            MyStruct* myStruct = new MyStruct();
    
            myStruct->intVector.push_back(5);
            myStruct->intVector.push_back(6);
            myStruct->intVectorSize  = myStruct->intVector.size();
            myStruct->intVectorPtr   = myStruct->intVector.data();
            
            myStruct->doubleVector.push_back(1.1);
            myStruct->doubleVector.push_back(2.3);
            myStruct->doubleVectorSize = myStruct->doubleVector.size();
            myStruct->doubleVectorPtr  = myStruct->doubleVector.data();
    
            return myStruct;
        }
    
        void delete_my_struct(MyStruct* myStruct) 
        {
            delete myStruct;
        }
    }
    

    and the java part:

    import com.sun.jna.Structure;
    import com.sun.jna.Native;
    import com.sun.jna.Pointer;
    
    import java.util.Arrays;
    
    import com.sun.jna.Library;
    import com.sun.jna.Structure.FieldOrder;
    
    public class BasicStruct 
    {
        public interface MyCppLibrary extends Library 
        {
            MyCppLibrary INSTANCE = (MyCppLibrary) Native.load("mycpp", MyCppLibrary.class);
    
            @FieldOrder({"intVectorSize", "doubleVectorSize", "intVectorPtr", "doubleVectorPtr"})
            public class MyStruct extends Structure 
            {
                public int intVectorSize;
                public int doubleVectorSize;
    
                public Pointer intVectorPtr;
                public Pointer doubleVectorPtr;
            }
    
            MyStruct create_my_struct();
            void delete_my_struct(MyStruct struct);
        }
    
        public static void main(String[] args) 
        {
            MyCppLibrary lib = MyCppLibrary.INSTANCE;
    
            MyCppLibrary.MyStruct myStruct = lib.create_my_struct();
    
            int[]    intArray    = myStruct.intVectorPtr   .getIntArray   (0, myStruct.intVectorSize);
            double[] doubleArray = myStruct.doubleVectorPtr.getDoubleArray(0, myStruct.doubleVectorSize);
    
            System.out.println("intVector contents   : " + Arrays.toString(intArray));
            System.out.println("doubleVector contents: " + Arrays.toString(doubleArray));
    
            lib.delete_my_struct(myStruct);
        }
    }
    

    Possible output:

    intVector contents   : [5, 6]
    doubleVector contents: [1.1, 2.3]