Transmit Structure Array from Java to C# using JNA and DLLExport

Viewed 161

I'm trying to transmit and receive a Structure Array with JNA from C# to/from Java. Receiving from C# in Java works fine, but transmitting to C# gives only one Line. I think the Problem is "pointerByReference.setPointer(array[0].getPointer())". But I don´t know how to create the PointerByReference for an Array to populate all Items. Can someone help me?

C#

using System;
using System.Runtime.InteropServices;

namespace JNATest
{
    public class Class1
    {
        [StructLayout(LayoutKind.Sequential)]
        public struct Struct
        {
            public int x;
            public int y;
            public int z;
            public string name;
        }

        [DllExport]
        public static int getStructureArray(out Struct[] structureArray)
        {
            structureArray = new Struct[] {
                new Struct { x = 12345, y = 99, z = 65432, name = "Hello from C# 1" },
                new Struct { x = 4423, y = 44, z = 31, name = "Hello from C# 2" },
                new Struct { x = 65233, y = 1244, z = 323, name = "Hello from C# 3" },
            };
            return structureArray.Length;
        }

        [DllExport]
        public static void setStructureArray(Struct[] structureArray)
        {
            Console.WriteLine("Length: " + structureArray.Length);
            for (int i = 0; i < structureArray.Length; i++)
            {
                Console.WriteLine(structureArray[i].x);
                Console.WriteLine(structureArray[i].y);
                Console.WriteLine(structureArray[i].z);
                Console.WriteLine(structureArray[i].name);
            }
        }
    }
}

Java Interface

public interface IJNA extends Library { IJNA INSTANCE = (IJNA) Native.load("JNATest.dll", IJNA.class);

    @FieldOrder({ "x", "y", "z", "name" })
    public class Struct extends Structure {
        public int x;
        public int y;
        public int z;
        public String name;

        public Struct() {
        }

        public Struct(Pointer pointer) {
            super(pointer);
            read();
        }

        public Struct(Pointer pointer, int offset) {
            super(pointer.share(offset));
            read();
        }

        public Struct(Struct struct) {
            super(struct.getPointer());
            read();
        }
    
        public static class ByReference extends Struct implements Structure.ByReference {
            public ByReference(Pointer pointer) {
                super(pointer);
            }
        }

        public static class ByValue extends Struct implements Structure.ByValue {
        }
    }

    public int getStructureArray(PointerByReference structureArray);

    public void setStructureArray(PointerByReference structureArray);

Java Main

import com.sun.jna.ptr.PointerByReference;

public class Main {

    public static void main(String[] args) {
        getStructureArray();
        setStructureArray();
    }

    public static void getStructureArray() {
        System.out.println("--- getStructureArray ------------------------------------------------");
        PointerByReference pointerByReference = new PointerByReference();
        int length = IJNA.INSTANCE.getStructureArray(pointerByReference);
        IJNA.Struct.ByReference structure = 
            new IJNA.Struct.ByReference(pointerByReference.getValue());
        IJNA.Struct.ByReference structures[] = (IJNA.Struct.ByReference[]) structure.toArray(length);
        System.out.println("Length: " + length);
        for (int i = 0; i < structures.length; i++) {
            structure = structures[i];
            System.out.println("x: " + structure.x);
            System.out.println("y: " + structure.y);
            System.out.println("z: " + structure.z);
            System.out.println("name: " + structure.name);
        }
    }

    public static void setStructureArray() {
        System.out.println("--- setStructureArray ------------------------------------------------");
        IJNA.Struct[] array = new IJNA.Struct[5];
        for (int i = 0; i < array.length; i++) {
            array[i] = new IJNA.Struct();
            array[i].x = 1;
            array[i].y = 2;
            array[i].z = 3;
            array[i].name = "Hello from Java " + (i + 1);
            array[i].write();
        }

        PointerByReference pointerByReference = new PointerByReference();
        pointerByReference.setPointer(array[0].getPointer());

        IJNA.INSTANCE.setStructureArray(pointerByReference);
    }
}

Output:

--- getStructureArray ------------------------------------------------
Length: 3
x: 12345
y: 99
z: 65432
name: Hello from C# 1
x: 4423
y: 44
z: 31
name: Hello from C# 2
x: 65233
y: 1244
z: 323
name: Hello from C# 3
--- setStructureArray ------------------------------------------------
Length: 1
1
2
3
Hello from Java 1

EDIT: I changed my code, but still get the same result.

@FieldOrder({ "x", "y", "z", "name" })
public class Struct extends Structure {
    public int x;
    public int y;
    public int z;
    public String name;
}

public static void setStructureArray() {
    System.out.println("--- setStructureArray ------------------------------- 
    -----------------");
    IJNA.Struct struct = new IJNA.Struct();
    IJNA.Struct[] array = new IJNA.Struct[5];
    long size = struct.size();
    Memory memory = new Memory(array.length * size);
    for (int i = 0; i < array.length; i++) {
        array[i] = IJNA.Struct.newInstance(IJNA.Struct.class, memory.share(i 
                   * size, size));
        array[i].x = 1;
        array[i].y = 2;
        array[i].z = 3;
        array[i].name = "Hello from Java " + (i + 1);
    }
    IJNA.INSTANCE.setStructureArray(array);     
}

Output:

--- setStructureArray ------------------------------------------------
Length: 1
1
2
3
Hello from Java 1

EDIT 2:

I found the Solution by my self:

Only add the MarshalAs Argument to the Method works.

C#

[DllExport]
    public static void setStructureArray([In, Out, 
        MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] Struct[] 
        structureArray, int length)
    {
        Console.WriteLine("Length: " + structureArray.Length);
        for (int i = 0; i < structureArray.Length; i++)
        {
            Console.WriteLine(structureArray[i].x);
            Console.WriteLine(structureArray[i].y);
            Console.WriteLine(structureArray[i].z);
            Console.WriteLine(structureArray[i].name);
        }
    }
2 Answers

In C, arrays are "flat" consisting of a single block of contiguous memory.

You have defined your array on the Java side like this:

IJNA.Struct[] array = new IJNA.Struct[5];
for (int i = 0; i < array.length; i++) {
            array[i] = new IJNA.Struct();
...
}

This results in 5 structures, each with non-contiguous memory backing on the native side. When you pass the pointer to the first element of the array, the native side has no idea where the other elements of the array are.

You need to use Structure.toArray() on the Java side to allocate contiguous memory:

IJNA.Struct[] array = (IJNA.Struct[]) new IJNA.Struct().toArray(5);
for (int i = 0; i < array.length; i++) {
        // array[i] = new IJNA.Struct(); <-- no longer needed
...
}

This declares backing memory with enough room for all 5 structures and internally also instantiates the Java-side objects, each with reference to their own starting memory address:

// This is internal to Structure.toArray. 
// You do not need to implement this yourself
array[i] = newInstance(getClass(), memory.share(i*size, size));

You can still pass the address to the [0] element as you do now, but the difference is that the other elements actually are where the native side expects them to be, at an offset of the first structure's size.

I found the Solution by my self:

Only add the MarshalAs Argument to the Method works.

C#

[DllExport]
public static void setStructureArray([In, Out, 
    MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] Struct[] 
    structureArray, int length)
{
    Console.WriteLine("Length: " + structureArray.Length);
    for (int i = 0; i < structureArray.Length; i++)
    {
        Console.WriteLine(structureArray[i].x);
        Console.WriteLine(structureArray[i].y);
        Console.WriteLine(structureArray[i].z);
        Console.WriteLine(structureArray[i].name);
    }
}
Related