3

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.

WWZee
  • 494
  • 2
  • 8
  • 23
  • 1
    http://stackoverflow.com/questions/7051097/return-a-stdwstring-from-c-into-c-sharp – pm100 May 09 '17 at 15:29
  • 1
    @pm100: Good related question, but it isn't a duplicate since this one actually has (unfortunately) `std::wstring` in the function signature, not just used internally. – Ben Voigt May 09 '17 at 15:31

1 Answers1

3

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.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • I had the single string idea too which I was explicitly told i shouldn't/can't do – WWZee May 09 '17 at 17:45
  • I don't understand how to use the BSTR, I've never seen that before. Could you provide and example please? – WWZee May 09 '17 at 19:05
  • 1
    I work with Wayne, and I can tell you that he's already using C++/CLI. – DCShannon May 09 '17 at 21:20
  • 1
    @DCShannon: Well that makes it a lot easier. In the C++/CLI class, you can return a `cli::array^` which is just the C++ name for C#'s `string[]` (SAFEARRAY would be automatically converted, this already is the C# type), and `System::String` has a constructor which takes the size (`wstr.size()`) and pointer (`&wstr[0]`). – Ben Voigt May 09 '17 at 21:53
  • @BenVoigt Thank you very much for the help. Might be a typo, but I couldn't get it to work the way you have it. I needed to put the `DllImport` and `return` parts in two seperate `[ ]` blocks – WWZee May 10 '17 at 17:32