Search code examples
javac#jna

Marshal.StructureToPtr Java equivalent in JNA


I am working on a project where the only lib I have to communicate to is a .dll file and the resource I have is a C# working project with source code; I am using JNA to access native codes and I currently run into Invalid memory access when I call the method;

c# code:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]  
public struct RSSlapInfo
{
    public int fingerType;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
    public RSPoint[] fingerPosition;
    public int imageQuality;
    public int rotation;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
    public int[] reserved;
};

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct RSSlapInfoArray
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
    public RSSlapInfo[] RSSlapInfoA;
};

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct RSPoint
{
    public int x;
    public int y;
};

RSSlapInfoArray slapInfoA = new RSSlapInfoArray();
IntPtr slapInfoArray;

int _size = Marshal.SizeOf(typeof(RSSlapInfoArray));
slapInfoArray = Marshal.AllocHGlobal(_size);
Marshal.StructureToPtr(slapInfoA, slapInfoArray, true);

On debug mode, I notice slapInfoArray value changes after the above call

My Java code:

public static class RSSlapInfo extends Structure
{
    public RSSlapInfo(){}
    public int fingerType;
    public RSPoint[] fingerPosition = new RSPoint[4];
    public int imageQuality;
    public int rotation;
    public int[] reserved =new int[3];
}
    
@Structure.FieldOrder({"RSSlapInfoA"})
public static class RSSlapInfoArray extends Structure implements Structure.ByValue
{
    public RSSlapInfoArray() {
    }

    public RSSlapInfo RSSlapInfoA[] = new RSSlapInfo[4];
}

@Structure.FieldOrder({"x","y"})
public static class RSPoint extends Structure
{
    public RSPoint(){
    }

    public int x;
    public int y;
}

Currently stuck on the right way to convert this C# part of the code before I make the native call with slapInfoArray

RSSlapInfoArray slapInfoA = new RSSlapInfoArray();
IntPtr slapInfoArray;
    
int _size = Marshal.SizeOf(typeof(RSSlapInfoArray));
slapInfoArray = Marshal.AllocHGlobal(_size);
Marshal.StructureToPtr(slapInfoA, slapInfoArray, true);

Solution

  • The documentation for Marshal.StructureToPtr() gives you a strong hint about what you need to do:

    Marshals data from a managed object to an unmanaged block of memory.

    structure A managed object that holds the data to be marshaled. This object must be a structure or an instance of a formatted class.

    ptr A pointer to an unmanaged block of memory, which must be allocated before this method is called.

    You have the structure parameter but you need to allocate the block of memory required for the second parameter. This can be done with JNA's Memory class.

    Complicating this, the structure you need to pass actually holds an array of other structures, and must be allocated in contiguous memory. The way you've done that in your RSSlapInfoArray wrapping doesn't actually allocate any memory at all; it just declares a java-side array with null pointers that would be passed to the native side.

    The first thing you need to do is correct your RSSlapInfoArray mapping to allocate the structure array contiguously. Change

    public RSSlapInfo RSSlapInfoA[] = new RSSlapInfo[4]; // does not allocate anything
    

    to

    public RSSlapInfo[] RSSlapInfoA = (RSSlapInfo[]) new RSSlapInfo().toArray(4); // contiguous allocation 
    

    You should do similarly for the RSPoint array:

    public RSPoint[] fingerPosition = new RSPoint().toArray(4);
    

    Another note on your structure mappings: you have overridden the Structure class constructor without implementing it. You should either delete your (unneeded) constructors or call super() inside the constructors.

    Assuming you've mapped StructureToPtr in an interface loading the DLL something similar this:

    void StructureToPtr(Structure structure, Pointer ptr, boolean fDeleteOld);
    

    Then the JNA code for your last 5 lines in your question should be

    // Create the managed structure with the array inline
    RSSlapInfoArray slapInfoA = new RSSlapInfoArray();
    // Allocate a block of memory for it
    Memory slapInfoArray = new Memory(slapInfoA.size());
    // Call StructureToPtr
    YourInterface.INSTANCE.StructureToPtr(slapInfoA, slapInfoArray, true);