none
Marshaling a struct containing a character buffer to be manipulated by unmanaged code (P/Invoke) RRS feed

  • Question

  • Hi all,
    I have an unmanaged dll that looks like this:

    struct MyStrStruct  
    {  
     char * buffer;  //forget about the buffer size for now, assume it's always 256 for the sake of example
    }  
     
    extern "C" 
    {  
     void TestStringInStruct(MyStrStruct* mss);  

    In the above function, mss is both an In and Out parameter (i.e. the unmanaged code should be able to both inspect its contents and manipulate it)

    I want to call that function via P/Invoke (using VS2008/.NET3.5 SP1, winXP), and the question is how to marshal the parameter.
    On one hand, MSDN's Default Marshaling for Strings article
     says (bottom of page, bolds are mine):

    Fixed-Length String Buffers

    In some circumstances, a fixed-length character buffer must be passed into unmanaged code to be manipulated. Simply passing a string does not work in this case because the callee cannot modify the contents of the passed buffer. Even if the string is passed by reference, there is no way to initialize the buffer to a given size. The solution is to pass a StringBuilder buffer as the argument instead of a string.

    So I write something like 

    [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]  
    class MyStrStruct //or 'struct'  
    {  
     StringBuilder buffer = new StringBuilder(256);  
    }  
     
    [DllImport("test.dll")]  
    public static extern int TestStringInStruct(MyStrStruct mss);  
     
    TestStringInStruct(new MyStrStruct()); 

    And get the following exception (bolds are mine):

    System.TypeLoadException was unhandled
      Message="Cannot marshal field 'buffer' of type 'MyStrStruct' : Struct or class fields cannot be of type StringBuilder. The same effect can usually be achieved by using a String field and preinitializing it to a string with length matching the length of the appropriate buffer."

    But the previous article stated, If I understood correctly, that the allocation of such a buffer is impossible!

    Lastly, there's this Microsoft confirmed bug (albeit labeled as .NET 1.0/1.1) that
    supports the latter exception description, providing code for such allocation as well:

    StringBuilder buffer = new StringBuilder("content", 100);   
    buffer.Append((char)0);  
    buffer.Append('*', buffer.Capacity - 8); //add anything to the end  
     
    MyStrStruct mss;  
    mss.buffer = buffer.ToString();  
    mss.size = mss.buffer.Length;  
    Win32.TestStringInStruct(ref mss); // call unmanaged function 

    So should I or should I not use strings as output buffers ?
    Also, if I do, must I use code similar to the above, or could I just use something like:

    MyStrStruct.buffer = new String("\0", 256)  
    //Or, if I want it pre-initiazlied with some string:  
    MyStrStruct.buffer = "hello" + new String("\0", 251) 

    The only explanation I can think of for the apparent inconsistency is that strings can't be used as parameters when marshaling, but as marshaled struct/class fields they can. It sounds strange to me, but what do I know...

    As a note, I have tried using strings as input and output buffers and it seemed to have worked, but obviously that doesn't mean it won't crash in the general case


    Thank you very much,
    Ohad

     

    • Edited by yosi142 Tuesday, December 23, 2008 3:46 PM Formatting
    Tuesday, December 23, 2008 3:40 PM

Answers

  • Try this:

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        private struct MyStrStruct {
          [MarshalAs(UnmanagedType.LPStr, SizeConst = 256)]
          public string buffer;
        }


    Hans Passant.
    • Marked as answer by yosi142 Thursday, December 25, 2008 12:15 PM
    Tuesday, December 23, 2008 4:39 PM
    Moderator

All replies

  • Just declare an IntPtr there. use Marshal.AllocCoTaskMem to allocate a block of memory from the COM task memroy allocator, use Marshal.StringToCoTaskMemAnsi (Since you have ANSI strings) to copy your string to the unmanaged COM memroy and pass the address to your unmanaged function. It should be happily accept the address since it is on the unmanaged heap and can modify the memory at will. After this function you use Marshal.PtrToStringAnsi to copy the memory back and use Marshal.FreeCoTaskMem to free the unmanaged memory. 

    You can write a marshaller class for your struct so you don't have to redo this everytime you call your function. Search for ICustomMarshaler for details.


    MSMVP VC++
    Tuesday, December 23, 2008 4:33 PM
  • Try this:

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        private struct MyStrStruct {
          [MarshalAs(UnmanagedType.LPStr, SizeConst = 256)]
          public string buffer;
        }


    Hans Passant.
    • Marked as answer by yosi142 Thursday, December 25, 2008 12:15 PM
    Tuesday, December 23, 2008 4:39 PM
    Moderator
  • Thank you both for the quick reply, I'll try and report back
    Wednesday, December 24, 2008 10:34 AM
  • Thanks nobugz, your solution worked
    Only I used
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] 
    public string globalId; 
    because I'm an MSDN Fanboy (http://msdn.microsoft.com/en-us/library/795sy883.aspx)

    Thanks again,
    Ohad
    Thursday, December 25, 2008 12:20 PM
  • Beware that ByValTStr gives you a char[], not a char*
    Hans Passant.
    Thursday, December 25, 2008 12:36 PM
    Moderator
  • Thanks, I forgot to mention that I changed the unmanaged struct's field to char[] as well
    • Edited by yosi142 Thursday, December 25, 2008 1:27 PM clarity
    Thursday, December 25, 2008 1:23 PM