Search code examples
c#c++marshallingintptr

How do I marshal a wstring * in C#?


I have a function in C++, _GetFileList, that I am trying to get to return a list/array of strings to C# code. I am not married to wstring * or any other type, this is just how the example I had found did it, but I am trying to get it to return a list/array and not just a single string. How can I marshal this to have multiple strings on the C# side?

C++ code:

extern "C" __declspec(dllexport) wstring * __cdecl _GetFileList(int &size){
    std::list<wstring> myList;
    //Code that populates the list correctly
    size = myList.size();
    wstring * arr = new wstring[size];
    for( int i=0;i<size;i++){
        arr[i] = myList.front();
        myList.pop_front();
    }
    return arr;
}

I call that from C# like this:

[DllImport(@"LinuxDirectory.Interface.dll")]
private static extern IntPtr _GetFileList(ref int size);

public bool GetFileList() {
    int size = 0;
    IntPtr ptr = _GetFileList( ref size );
    //WHAT DO I DO HERE TO GET THE LIST OF STRINGS INTO SOMETHING I CAN READ?
}

I've tried to use Marshal.PtrToStringUni, Marshal.PtrToStringAnsi, and the structure one as well but couldn't seem to get the syntax correct.


Solution

  • You don't. P/Invoke can only deal with function signatures that are C-compatible, meaning pointers have to be to plain-old-data, not full C++ classes.

    If you can rewrite the C++ code, change it to call SysAllocString and return a BSTR, which the OS-provided string type intended for data exchange between components written in different languages. P/invoke already has all the right magic for receiving a BSTR, just use the C# string type in the p/invoke declaration.

    If you can't change the C++ signature, you'll have to wrap it. Either use C++/CLI, which can deal with C++ classes as well as .NET, or use standard C++ and create and return a BSTR, copying data from the std::wstring.


    For multiple strings, you can use the OS-provided type for array (SAFEARRAY) which is able to carry BSTR inside and again is language independent. Or it may be easier to just join and split using an appropriate (not appearing naturally in the data) separator character.

    There's some really great information here: https://msdn.microsoft.com/en-us/magazine/mt795188.aspx

    Running .NET Framework (the Microsoft implementation, on Windows), you can use the helper classes:

    #include <windows.h>
    #include <atlbase.h>
    #include <atlsafe.h>
    #include <string>
    
    extern "C" __declspec(dllexport) LPSAFEARRAY GetFileList()
    {
        std::vector<std::wstring> myList;
        //Code that populates the list correctly
        size = myList.size();
    
        CComSafeArray<BSTR> sa(size);
        int i=0;
        for( auto& item : myList ) {
            CComBSTR bstr(item.size(), &item[0]);
            sa.SetAt(i++, bstr.Detach()); // transfer ownership to array
        }
        return sa.Detach(); // transfer ownership to caller
    }
    

    On the C# side, p/invoke handles it all:

    [DllImport("NativeDll.dll"),
     return:MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BSTR)]
    public static extern string[] GetFileList();
    

    Your question suggests you might be on Linux. Unfortunately, the documentation I found seems to say that Mono's p/invoke doesn't know what to do with variable-sized arrays at all. So you're stuck doing everything by hand, including deallocation (the code in your question leaks new wstring[size] -- C# has absolutely no clue how to call a C++ delete[] operator). Have a look at Mono.Unix.UnixMarshal.PtrToStringArray.