JasonA
JasonA

Reputation: 63

How do I populate a structure with pointers to pointers from Java?

I am trying to invoke a function in a C DLL that has a function that takes a pointer to a struct. This struct (Data) is defined like this:

struct BD
{
    char* Data;
    int Length;
};

struct EE
{
    char* Key;
    BD* Value;
};

struct Data
{
    char* Name;
    BD* Picture;

    // A NULL-terminated array of pointers to EE structures.
    EE** Elements;
};

In Java I have defined some classes that look like this:

public static class BD extends Structure implements Structure.ByReference {
    public byte[] Data;
    public int Length;
}

public static class EE extends Structure implements Structure.ByReference {
    public String Key;
    public BD Value;
}

public static class Data extends Structure {
    public String Name;
    public BD Picture;
    public PointerByReference Elements;
}

But now I don't know exactly how to populate the Data object correctly. I think I can figure out the Name and Picture fields but what do I set the Elements field to? I can create a Java array of EE objects but then how do I get a PointerByReference from that? Maybe I need to declare Elements as a Pointer[] but then would I just fill in each element of the array with the getPointer() for each EE object? That doesn't quite seem right, though?

Edit: To give a better idea of what I'm trying to do:

Data data = new Data();
// Fill in Name and Picture fields.

EE[] elements = new Elements[10];
// Fill in the elements array.

// Now how do I set the Elements field on the data object from the elements array?
data.Elements = ???

Edit2: Here's how I solved it with technomage's help:

I changed my Data structure to look like this:

public static class Data extends Structure {
    public String Name;
    public BD Picture;
    public Pointer Elements;
}

And my BD structure to look like this:

public static class BD extends Structure implements Structure.ByReference {
    public Pointer Data;
    public int Length;
}

To convert a Java byte[] to a JNA Pointer I had to use a ByteBuffer:

ByteBuffer buf = ByteBuffer.allocateDirect(bytes.length);
buf.put(bytes);
bd.Data = Natvie.getDirectBufferPointer(buf);

JNA doesn't like ByteBuffers in a struture unfortunately.

To get my elements pointer, I needed to create an array of Pointers to each EE object (see technomage's answer for PointerArray implementation):

EE e = new EE();
// Populate e object.
// ...

// Important: ensure that the contents of the objects are written out to native memory since JNA can't do this automatically
e.write();
ptrs.add(e);

// Once each object is setup we can simply take the array of pointers and use the PointerArray
data.Elements = new PointerArray(ptrs.toArray(new Pointer[0]));

I couldn't use ByteBuffer or PointerArray directly in the structure definition so I had to rely on Pointer.

Upvotes: 1

Views: 1827

Answers (1)

technomage
technomage

Reputation: 10069

Make Elements a Pointer, then use Pointer.getPointerArray(0) to retrieve the structure pointers of interest.

You'll then need to initialize each Structure from each retrieved Pointer (don't forget to call Structure.read()).

There is no way to automagically perform the indirection on the pointer and wind up with an array of Structure, but it's simple enough to encode into a method on your Structure class (e.g. Data.getElements()).

EDIT

Sample pointer array implementation, to initialize the pointer array on write (rather than read):

class PointerArray extends Memory {                      
        private final Pointer[] original;                                                           
        public PointerArray(Pointer[] arg) {                                                        
            super(Pointer.SIZE * (arg.length+1));                                                   
            this.original = arg;                                                                    
            for (int i=0;i < arg.length;i++) {                                                      
                setPointer(i*Pointer.SIZE, arg[i]);                                                 
            }                                                                                       
            setPointer(Pointer.SIZE*arg.length, null);                                              
        }                                                                                           
    }
}

You'll also need to call Structure.write() before calling the native function (you might do this as part of PointerArray), since JNA doesn't know that the memory needs to be synched when you use a vanilla Pointer instead of Structure.

Upvotes: 2

Related Questions