0

I am trying to store and retrieve some data into/from an unmanaged dll. I have tried to narrow down my problem by simplifying the struct as much as possible and here is what I am getting down to:

Structure definition

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public class MyStruct
{
  private UInt32 size;    
  public UInt16 SomeData;

  public MyStruct()
  {
    size = (UInt32)Marshal.SizeOf(this);
    this.SomeData = 66; //just put any non 0 value for test
  }
}

DLL imports:

[DllImport(MY_DLL, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
[return:MarshalAs(UnmanagedType.U1)]
public static extern bool SetData(ref MyStruct ms);
[DllImport(MY_DLL, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern IntPtr GetData();

Function calls:

MyStruct ms_in = new MyStruct();
bool b = Wrapper.SetData(ref ms_in);
IntPtr ptr = Wrapper.GetData();
MyStruct ms_out = (MyStruct)Marshal.PtrToStructure(ptr, typeof(MyStruct));

Simple enough I guess. I know that charset and packing are ok as I simply pasted the struct layout attributes from another struct definition for the same dll as I did for most of the code actually.

When reading the content of ms_out it is just full of garbage (random large numbers).

I finally found the answer to my question by trial and error but I can't understand it much. Here is the working version:

[DllImport(MY_DLL, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
[return:MarshalAs(UnmanagedType.I1)]
public static extern bool SetData( [In, MarshalAs(UnmanagedType.LPStruct)] MyStruct ms);

Replacing ref by [In, MarshalAs(UnmanagedType.LPStruct)] did the trick but why?

Thank you for your answers, happy coding.

pasx
  • 2,718
  • 1
  • 34
  • 26
  • 4
    Kinda self-inflicted by declaring a type named "MyStruct" as a class. A class object is already passed by reference. Your original declaration passed a MyStruct** instead of a MyStruct*. – Hans Passant Nov 16 '12 at 19:07
  • Thank you for the answer.I edited the original code for the post and since the class is supposed to mirror the unmanaged struct... But I agree it is bad naming. – pasx Nov 17 '12 at 17:07
  • @HansPassant I am trying to add a property to the class like: `[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MY_STRING_SIZE)] public string SomeString;` I set the property in the constructor to some string but on return the string is full of garbage and so are the other props. Do you know the reason for that? Thx – – pasx Nov 19 '12 at 09:56

2 Answers2

0

Investigating this further I found that marshalling can be used with both c# structs or classes as destination.

Using a struct:

[StructLayout...
struct MyStruct
{
  //some properties

  //can't have parameterless constructor
  public void MakeStruct()
  {
    size = ...;
    //initialize properties as needed
  }
}   
...
public static extern bool SetData(ref MyStruct ms); //ref is ok for struct, equivalent to c &struct

Using a class:

[StructLayout...
class MyClass
{
  //some properties

  //parameterless constructor
  public void MyClass()
  {
    size = ...;
    //initialize properties as needed
  }
}

...
public static extern bool SetData([In, MarshalAs(UnmanagedType.LPStruct)]  MyClass ms); //no ref for class

Using a class has the advantage of automatically setting the size in the constructor instead of the caller having to set it explicitly with the struct version.

pasx
  • 2,718
  • 1
  • 34
  • 26
0

Now why is the structure being full of garbage on return from the unmanaged dll?

This occurs when I set the data on the unmanaged dll and then get it back but not when I locally create an unmanaged pointer, set the data and read them back:

MyClass x = new MyClass(); //create class
IntPtr ptr2 = Marshal.AllocHGlobal(Marshal.SizeOf(x)); //allocate unmanaged memory
Marshal.StructureToPtr(x, ptr2, false); //marshall to unmanaged memory
MyClass xOut = (MyClass)Marshal.PtrToStructure(ptr2, typeof(MyClass)); //marshall from unmanaged memory
Marshal.FreeHGlobal(ptr2); //free unmanaged memory

If your data "survives" the above test then all the StructLayout, charset, marshalling, etc is OK. In my case it is.

If you simply create the data in c# and pass a pointer to it to the unmanaged code you are only certain that the pointer address will be valid, the data it points to will be invalid as soon as the c# variable is out of scope.

The following method will work with any kind of data I tested including strings:

[return:MarshalAs(UnmanagedType.U1)]
public static extern bool SetData( IntPtr data); //no marshalling in, no ref, no problem...

MyClass x = new MyClass(); //create class
//...store some data in x....
IntPtr ptrIn = Marshal.AllocHGlobal(Marshal.SizeOf(x)); //allocate unmanaged memory
Marshal.StructureToPtr(x, ptrIn, false);    //marshall to unmanaged memory

bool b = Wrapper.SetData(ref ms_in); //store data in unmanaged dll
IntPtr ptrOut = Wrapper.GetData(); //get data back from unmanaged dll

MyClass xOut = (MyClass)Marshal.PtrToStructure(ptrOut, typeof(MyClass)); //marshall from unmanaged memory

Marshal.FreeHGlobal(ptrIn); //free unmanaged memory

The problem here is that the caller must release the memory allocated. In my scenario this is used to pass the data on to yet another unmanaged dll (don't ask...) but the calling program has no way to make sure that the final recipient has actually read the data.

Who is going to take care of cleaning up the memory in the end?

I will probably go with the callee cleaning up the data and the caller checking that the data is released/cleaning if not before creating a new block and same thing on exit unless I find a scenario a bit simpler.

pasx
  • 2,718
  • 1
  • 34
  • 26
  • Rule number 1 of coding: as long as you only understand part of the problem you are only hacking (: – pasx Nov 19 '12 at 11:42