HOWTO: [DotNet] marshale a managed string array for Interop

Normally if you have defined a struct in C# which is for a Win32 call, you might tend to copy-paste it from C++, and then modify LPWSTR to string and add attributes (etc),
like this:

struct myStruct
{
   [MarshalAs(UnmanagedType.LPWstr)}
   public string blah; //ok
   [MarshalAs(UnmanagedType.ByValArray, ArraySubType=UnmanagedType.LPWStr)]   
    public string[] stringarray;
}

Let's concentrate on the array definition. The original array was defined like LPWSTR* stringarray;  this is an array of pointers to zero-terminated strings.

Now you thought, that a few attributes, would be sufficient to support your interop?

[MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPWStr)]   
public string[] stringarray;

but the .NET framework, won't support you here. It is not able to create an unmanaged array of 'some type' on structs (this however, is working for parameter definitions) and to pass it to your unmanaged API.

And if you wonder what UnmanagedType.ByValArray, UnmanagedType.LPWStr  really does; it creates a pointer to a contigous block of zero terminated strings. It would not create an array of stringpointers. So now you know, why interop is an art, not a science :)

At last, I wrote an easy wrapper. It returns an IntPtr which is a just pointer to our first element to an array of string pointers :)

You should assign this class as a member variable on your own class. If you use this class inside a member function, your unmanaged memory might be cleaned when the function goes out of scope.
PS: Please send back the improved code, if you decide to use it (at your own risk of course). It might be improved, to pack managed structures as well, using C# generics.

Sample:

class myClass()

{   private packLPArray packit;      

      void myMemberfunction()
      {   // ... do your dangerous, endeavourish :) interop thing here
            packit = new packLPArray(new string[] {"element1", "element2", "blah"});
            mystruct.stringarray = packit.arrayPtr;
      }

}

 

///

packs an array of strings (type = string[]) to unmanaged memory
/// also works for 64-bit environments
///

public sealed class  packLPArray
{
    private IntPtr taskAlloc;

    private readonly int _length;

    private IntPtr[] _strings;

    public packLPArray(string[] theArray)

    {   int sizeIntPtr = IntPtr.Size;

        int neededSize = 0;

        if (theArray != null)

        {

            this._length = theArray.Length;

            this._strings = new IntPtr[this._length];

           // System.Diagnostics.Debugger.Break();

            neededSize = this._length * sizeIntPtr;

            this.taskAlloc = Marshal.AllocCoTaskMem(neededSize);           

            for (int cx = this._length - 1; cx >= 0; cx--)
            {   this._strings[cx] = Marshal.StringToCoTaskMemUni(theArray[cx]);
                Marshal.WriteIntPtr(this.taskAlloc, cx * sizeIntPtr, this._strings[cx]);

            }

        }

    }

    ///

    /// retrieves array length

    ///

    public int Length

    {

        get { return _length; }

    }

    public IntPtr arrayPtr

    {

        get { return this.taskAlloc; }

 

    }

    ~packLPArray() // clean up the rub

    {

        if (taskAlloc != IntPtr.Zero)

        {

            Marshal.FreeCoTaskMem(this.taskAlloc);

            int cx = this._length;

            while(cx-- != 0)

                Marshal.FreeCoTaskMem(this._strings[cx]);

        }

    } 

}

blog comments powered by Disqus