views:

379

answers:

2

I am having some trouble marshaling a pointer to an array of strings. It looks harmless like this:

typedef struct
{
    char* listOfStrings[100];
} UnmanagedStruct;

This is actually embedded inside another structure like this:

typedef struct
{
    UnmanagedStruct umgdStruct;
} Outerstruct;

Unmanaged code calls back into managed code and returns Outerstruct as an IntPtr with memory allocated and values filled in.

Managed world:

[StructLayout(LayoutKind.Sequential)]
public struct UnmanagedStruct
{
    [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStr, SizeConst=100)]
    public string[] listOfStrings;
}

[StructLayout(LayoutKind.Sequential)]
public struct Outerstruct
{
    public UnmanagedStruct ums;
}

public void CallbackFromUnmanagedLayer(IntPtr outerStruct)
{
    Outerstruct os = Marshal.PtrToStructure(outerStruct, typeof(Outerstruct));
    // The above line FAILS! it throws an exception complaining it cannot marshal listOfStrings field in the inner struct and that its managed representation is incorrect!
}

If I change listOfStrings to simply be an IntPtr then Marshal.PtrToStructure works but now I am unable to rip into listOfStrings and extract the strings one by one.

+1  A: 

Marshalling anything but a very basic string is complex and full of side cases that are hard to spot. It's usually best to go with the safe / simple route in the struct definition and add some wrapper properties to tidy things up a bit.

In this case I would go with the array of IntPtr and then add a wrapper property that converts them to strings

[StructLayout(LayoutKind.Sequential)]
public struct UnmanagedStruct
{
    [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStr, SizeConst=100)]
    public IntPtr[] listOfStrings;

    public IEnumerable<string> Strings { get { 
      return listOfStrings.Select(x =>Marshal.PtrToStringAnsi(x));
    }
}
JaredPar
JaredThanks for validating! I posted an answer to my own question just now before seeing yours. One question -- how do I format my code in posts? They all looked messed up and someone has to edit and correct it all the time.
Dilip
@Dilip, select your code snippet and hit CTRL+K. That will fix the formatting by indenting everything 4 spcaes
JaredPar
@Jared: just a quick followup. The code continues to bomb if I use UnmanagedType.LPArray. Only UnmanagedType.ByValArray works. I now understand what KeeperOfTheSoul was hinting at in his/her comments.
Dilip
+1  A: 

OK.. I seem to have got it to work. It should be marshaled as IntPtr[]

This seems to work:

[StructLayout(LayoutKind.Sequential)] 
public struct UnmanagedStruct 
{ 
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=100)] 
    public IntPtr[] listOfStrings; 
}

for (int i = 0; i < 100; ++i)
{
    if (listOfstrings[i] != IntPtr.Zero)
        Console.WriteLine(Marshal.PtrToStringAnsi(listOfStrings[i]));
}
Dilip
ByValArray == in-place array, LPArray == a pointer to an array. Though SizeConst still should work with an LPArray, so the error when marshaling was a little odd.
KeeperOfTheSoul
Oh, that should also work if you have public string[] listOfStrings, its the ByValArray that makes the difference I believe.
KeeperOfTheSoul