Return Contents of a Std::Wstring from C++ into C#

Return contents of a std::wstring from C++ into C#

You are going to have to rewrite your getMyString function for the reasons mentioned in Hans Passant's answer.

You need to have the C# code pass a buffer in to your C++ code. That way the your code (ok, the CLR Marshaller) controls the lifetime of the buffer and you don't get into any undefined behavior.

Below is an implementation:

C++

void getMyString(wchar_t *str, int len)
{
wcscpy_s(str, len, someWideString.c_str());
}

C#

[DllImport( "my.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode )]
private static extern void GetMyString(StringBuffer str, int len);
public string GetMyStringMarshal()
{
StringBuffer buffer = new StringBuffer(255);
GetMyString(buffer, buffer.Capacity);
return buffer.ToString();
}

How to return std::wstring from C++ to C# using PInvoke without writing stringbuffer conversion

In your Node.cpp file, use SysAllocString from oleauto.h (or include Windows.h) to allocate the string for you like this:

MYAPI BSTR GetNodeName(NativeCore::Node* obj)
{
if (obj != NULL)
return SysAllocString(obj->GetName().c_str());
return NULL;
}

Then adjust your native method wrapper to use UnmanagedType.BStr instead of UnmanagedType.LPWStr:

class UnMangedWrapper 
{
[DllImport("NativeCore.dll", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.BStr)]
public static extern string GetNodeName(IntPtr ptr);
}

Using BSTR has the advantage that you don't have to call into the unmanaged code twice (once for the buffer length and another time for the actual string content) and the marshaller can automatically take care of deallocating the unmanaged string.

Call a wstring C++ method from C#

You may find this question and this question to be relevant. There are two issues:

  1. P/Invoke does not natively marshal std::string/std::wstring, and
  2. Possible memory lifetime issues (depends on implementation of CWirelessHelper::DecryptData).

An approach is to copy the string to a plain wchar_t* buffer allocated using CoTaskMemAlloc (the framework will handle the string conversion and free the allocated memory).

On the unmanaged side, the code becomes:

extern "C" __declspec(dllexport) const wchar_t* DecryptData( wchar_t* strKey) {
std::wstring retstr = WlanHelper::CWirelessHelper::DecryptData(std::wstring(strKey));
const wchar_t* ret = retstr.c_str();
size_t bufsize = wcslen(ret) + 1;
wchar_t* buffer = (wchar_t*)CoTaskMemAlloc(bufsize * sizeof(wchar_t));
wcscpy_s(buffer, bufsize, ret);
return buffer;
}

And on the managed side:

[DllImport(DLL_PATH, 
CharSet = CharSet.Unicode,
CallingConvention = CallingConvention.Cdecl)]
static extern string DecryptData(string strKey);

How do I marshal a wstring * in C#?

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.

Issue with std::wstring when calling from c# with DllImport

This is something I noticed:

[DllImport("test.exe", CallingConvention=CallingConvention.Cdecl)]

Exporting functions from an EXE instead of a DLL isn't standard. (I think it can be done, but I wouldn't recommend it.)

Build your C++ code as a DLL instead of an EXE. Statically link the DLL with the C++ runtime libraries to avoid missing dependency issues that could arise from not having the right DLLs in the loader search path.

Unable to get result of std::string function in C++ to C# Interop

std::string will never work here as the marshaller does not know how to free it.

Instead, you need to pass in a buffer from the C# side, like this

    class Program
{
[DllImport(@"CPPSample.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi,EntryPoint = "SM_Interop_API_Add")]
public static extern int SM_Interop_API_Add(int a, int b);

[DllImport(@"CPPSample.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, EntryPoint = "SM_Interop_API_getLanguage")]
public static extern void SM_Interop_API_getLanguage(StringBuilder buffer)

static void Main(string[] args)
{
Console.WriteLine("Hello World!");
int sum = SM_Interop_API_Add(10, 10);
Console.WriteLine($"Result: {sum}");
var result = new StringBuilder(200);
SM_Interop_API_getLanguage(result);
Console.WriteLine($"Sm Language: {result}");
Console.WriteLine("----EOP----");
}
}

Then on the C++ side, you simply copy it in (not great with C++, guessing a bit)

    __declspec(dllexport) void SM_Interop_API_getLanguage(char* buffer)
{
strcpy(buffer, "This is Test String");
//return getLanguage();
}

Passing a buffer size and checking that it's large enough would be wise also.

Return string from c++ dll export function called from c#

How about this (Note, it assumes correct lengths - you should pass in the buffer length and prevent overflows, etc):

extern "C" __declspec(dllexport)  void  __cdecl getDataFromTable(char* tableName, char* buf)
{
std::string st = getDataTableWise(statementObject, columnIndex);
printf(st.c_str());

strcpy(buf, st.c_str());
}

Then in C#:

[DllImport("\\SD Card\\ISAPI1.dll")]
private static extern string getDataFromTable(byte[] tablename, byte[] buf);
static void Main(string[] args)
{
byte[] buf = new byte[300];
getDataFromTable(byteArray, buf);
Console.writeLine(System.Text.Encoding.ASCII.GetString(buf));
}

Note, this does make some assumptions about character encodings in your C++ app being NOT unicode. If they are unicode, use UTF16 instead of ASCII.

Returning a std::string from a C++ DLL to a c# program - Invalid Address specified to RtlFreeHeap

I managed to find the issue. It was the way the C# definition was done. From what I can understand, using the MarshallAs(UnmanagedType.LPStr) in combination with the string return type makes it so that it'll attempt to free the string when its done. But because the string comes from the C++ DLL, and most likely a totally different memory manager, it fails. And even if it didn't fail, I don't want it to be freed anyway.

The solution I found was to change the C# declaration to this (the C++ code is unchanged):

[DllImport("MyDll", EntryPoint = "GetDLLName")]
public static extern IntPtr GetDLLName();

So this makes it so that it just returns a pointer to the string data. And then to change it to a string, pass it to Marshal.PtrToStringAnsi()

return Marshal.PtrToStringAnsi(GetDLLName());

And that gets wrapped into another function for cleanliness.

I found the solution from this page:
http://discuss.fogcreek.com/dotnetquestions/default.asp?cmd=show&ixPost=1108



Related Topics



Leave a reply



Submit