Eugene
Eugene

Reputation: 179

Marshaling structure field which is a pointer to an array of structures

I have spent some time thinking about this problem, and I need your help. My question looks somewhat similar to many on Stack Overflow, and I looked through many of them, and still didn't find an answer.

I need to marshal a structure field which is a pointer to an array of structures. The catch is that I need this structure to be received from a legacy external library written in C. I've found many tips about passing structures as pointers by locking memory of correct size using Marshal.AllocHGlobal and Marshal.StructureToPtr. I've also found guides how to receive structure which is returned as a pointer: you can use [MarshalAs] modifiers. Unfortunately, that won't help because most of the [MarshalAs] modifiers seem to be inapplicable to structure fields.

In the example below, I am able to receive needed data in two steps:

  1. Receive MiddleStruct data and a pointer inside it
  2. Read pointed data by using PtrToStructure

The problem is I want to be sure that the garbage collector does not modify the memory which is referenced by the received pointer. Is there any way I can read data in the InnerStructure using one step?

C structures

typedef struct OuterStruct {
    MiddleStruct mStruct;
} OuterStruct;

typedef struct MiddleStruct {
    int count;
    InnerStruct FAR *innerData;
} MiddleStruct;

typedef struct InnerStruct {
    int number;
    char data[64];
} InnerStruct;

C# structures

[StructLayout(LayoutKind.Sequential, Pack = 4)]
public class OuterStruct
{
    public MiddleStruct mStruct;
};

[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct MiddleStruct
{
    public int count;
    public IntPtr pIStruct; //pointer to array of InnerStruct
}

[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct InnerStruct
{
    int number;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)]
    public bytes[] data;
};

Function

[DllImport("legacy.dll", CharSet = CharSet.Ansi)]
public static extern short getData([In, Out, MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(myCustomMarshaler))] OuterStruct sStruct);

Example

OuterStruct oStruct;
getData(out oStruct);
int count = oStruct.mStruct.count;
IntPtr pIStruct = oStruct.mStruct.pIStruct;
InnerStruct[] iStructArray = new InnerStruct[count];
int infoSize = Marshal.SizeOf(new iStructArray());
for (int i = 0; i < count; i++)
{
    IntPtr targetPtr = new IntPtr(pIStruct.ToInt32() + infoSize * i);
    iStructArray[i] = (InnerStruct)Marshal.PtrToStructure(targetPtr, typeof(InnerStruct));
}
WriteLine(iStructArray);

Update:

I've created the custom marshaller, but I can't think of a way to return the value. Knowing that array is reference type, is there any way to cast intPtr to array? I tried to use object instead of intPtr and cast it accordingly, that didn't work.

public class myCustomMarshaler : ICustomMarshaler
{
    [ThreadStatic]
    private OuterStruct marshaledObj;

    private static myCustomMarshaler marshaler = null;
    public static ICustomMarshaler GetInstance(string cookie)
    {
        if (marshaler == null)
        {
            marshaler = new myCustomMarshaler();
        }
        return marshaler;
    }

    #region ICustomMarshaler Members
    public int GetNativeDataSize()
    {
        return Marshal.SizeOf(typeof(OuterStruct));
    }

    public object MarshalNativeToManaged(System.IntPtr pNativeData)
    {
        Marshal.PtrToStructure(pNativeData, this.marshaledObj);
        int count = this.marshaledObj.mStruct.count;
        IntPtr pIStruct = this.marshaledObj.mStruct.pIStruct;
        InnerStruct[] iStructArray = new InnerStruct[count];
        int dataSize = Marshal.SizeOf(new InnerStruct());
        for (int i = 0; i < count; i++)
        {
            iStructArray[i] = (InnerStruct)Marshal.PtrToStructure(new IntPtr(pIStruct.ToInt32() + dataSize * i), typeof(InnerStruct));
        }

        //*** how do I include iStructArray in return?

        return this.marshaledObj;
    }

    public System.IntPtr MarshalManagedToNative(object managedObj)
    {
        if (!(managedObj is OuterStruct))
        {
            throw new ArgumentException("Specified object is not a OuterStruct object.", "managedObj");
        }
        else
        {
            this.marshaledObj = (OuterStruct)managedObj;
        }
        IntPtr ptr = Marshal.AllocHGlobal(this.GetNativeDataSize());
        if (ptr == IntPtr.Zero)
        {
            throw new Exception("Unable to allocate memory to.");
        }
        Marshal.StructureToPtr(this.marshaledObj, ptr, false);

        return ptr;
    }

    public void CleanUpManagedData(object managedObj)
    {
    }

    public void CleanUpNativeData(System.IntPtr pNativeData)
    {
        Marshal.FreeHGlobal(pNativeData);
    }

    #endregion
}

Upvotes: 3

Views: 1741

Answers (1)

Eugene
Eugene

Reputation: 179

I solved my problem by using another class to wrap it around oStruct and adding a dictionary field for returning additional data. While it worked for me, it is very unconvenient to use and does not scale well. Anyway, maybe this can be of use for someone:

Wrapper class

public class WrapperClass
{
    public OuterStruct oStruct;
    public Dictionary<string, object> auxData;

    public WrapperClass()
    {
        this.oStruct = new OuterStruct();
        this.auxData = new Dictionary<string, object>();
    }
}

Custom marshaler

public class myCustomMarshaler : ICustomMarshaler
{
    [ThreadStatic]
    private WrapperClass marshaledObj;

    private static myCustomMarshaler marshaler = null;
    public static ICustomMarshaler GetInstance(string cookie)
    {
        if (marshaler == null)
        {
            marshaler = new myCustomMarshaler();
        }
        return marshaler;
    }

    public int GetNativeDataSize()
    {
        return Marshal.SizeOf(typeof(OuterStruct));
    }

    public object MarshalNativeToManaged(System.IntPtr pNativeData)
    {
        Marshal.PtrToStructure(pNativeData, this.marshaledObj);
        int count = this.marshaledObj.mStruct.count;
        IntPtr pIStruct = this.marshaledObj.mStruct.pIStruct;

        InnerStruct[] iStructArray = new InnerStruct[count];
        int dataSize = Marshal.SizeOf(new InnerStruct());
        for (int i = 0; i < count; i++)
        {
            iStructArray[i] = (InnerStruct)Marshal.PtrToStructure(new IntPtr(pIStruct.ToInt32() + dataSize * i), typeof(InnerStruct));
        }
        // Add additional data to wrapper
        this.marshaledObj.auxData.Add("iStructArray", iStructArray);
        return this.marshaledObj;
    }

    public System.IntPtr MarshalManagedToNative(object managedObj)
    {
        if (!(managedObj is WrapperClass))
        {
            throw new ArgumentException("Specified object is not a WrapperClass object.", "managedObj");
        }
        else
        {
            this.marshaledObj = (WrapperClass)managedObj;
        }
        IntPtr ptr = Marshal.AllocHGlobal(this.GetNativeDataSize());
        if (ptr == IntPtr.Zero)
        {
            throw new Exception("Unable to allocate memory to.");
        }
        Marshal.StructureToPtr(this.marshaledObj.oStruct, ptr, false);

        return ptr;
    }

    public void CleanUpManagedData(object managedObj)
    {
    }

    public void CleanUpNativeData(System.IntPtr pNativeData)
    {
        Marshal.FreeHGlobal(pNativeData);
    }
}

Usage

[DllImport("legacy.dll", CharSet = CharSet.Ansi)]
public static extern short getData([In, Out, MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(myCustomMarshaler))] OuterStruct wrapper);

Upvotes: 1

Related Questions