Returning a String from Pinvoke

Returning a string from PInvoke?

First of all, as others have pointed out, your C++ is broken even before trying interop. You are returning a pointer to stri's buffer. But because stri is destroyed as soon as the function returns, the return value is not valid.

What's more, even if you fixed this, you need to do more. It won't work allocating memory in your C++ code which you would need the C# code to deallocate.

There are a few options to do it right.

Your C# code can ask the C++ code how long the string is. Then a C# StringBuilder is created and allocated to the appropriate size. Next the StringBuilder object is passed to the C++ code and its default marshalling is as a LPWSTR. In this approach the C# code allocates the string and your C++ code receives a C string to which it must copy the buffer.

Alternatively you can return a BSTR from the C++ which allows allocation in the native C++ code and deallocation in the C# code.

The BSTR approach is probably how I would do it. It looks like this:

C++

#include <comutil.h>
BSTR GetSomeText()
{
return ::SysAllocString(L"Greetings from the native world!");
}

C#

[DllImport(@"test.dll", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.BStr)]
private static extern string GetSomeText();

Update

Hans Passant added a couple of useful observations in the comments. First of all, most P/Invoke interop is done against an existing interface which cannot be changed and you do not have the luxury of picking your preferred interop interfacing approach. It would appear that is not the case here, so which approach should be chosen?

Option 1 is to allocate the buffer in the managed code, after having first asked the native code how much space is needed. Perhaps it is enough to use a fixed size buffer that both parties agree on.

Where option 1 falls down is when assembling the string is expensive and you don't want to do it twice (e.g. once to return its length, and once again for the contents). This is where option 2, the BSTR comes into play.

Hans pointed out one drawback of the BSTR, namely that it carries a UTF-16 payload but your source data may well char*, which is a "bit of a hassle".

To overcome the hassle you can wrap up the conversion from char* to BSTR like this:

BSTR ANSItoBSTR(char* input)
{
BSTR result = NULL;
int lenA = lstrlenA(input);
int lenW = ::MultiByteToWideChar(CP_ACP, 0, input, lenA, NULL, 0);
if (lenW > 0)
{
result = ::SysAllocStringLen(0, lenW);
::MultiByteToWideChar(CP_ACP, 0, input, lenA, result, lenW);
}
return result;
}

That's the hardest one out of the way, and now it's easy to add other wrappers to convert to BSTR from LPWSTR, std::string, std::wrstring etc.

How to return string array using Pinvoke

Without more information about your specific case, it's impossible to say how you'd marshal the return value. Perhaps this page of interop marshaling samples will help: http://msdn.microsoft.com/en-us/library/e765dyyy.aspx

The above link no longer works. Try Marshaling Data with Platform Invoke.

Why can't I return a char* string from C++ to C# in a Release build?

Or maybe try to use

[DllImport("SimpleDll")]
public static extern IntPtr ReturnString();

and in your calling code, use the Marshal Class

string ret = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(PInvoke.ReturnString());

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.

Return string from unmanaged dll to C#

I have reached an answer, which I will record here for completeness, with grateful thanks to all those who pointed me in the right direction.

According to this post elsewhere, the use of .NET Reflector on similar VB code suggests the need to use the string type in place of my StringBuilder, as suggested here by Alex Mendez, JamieSee and Austin Salonen, together with explicit marshaling, as suggested by Nanhydrin, but utilising the unmanaged type VBByRefStr rather than AnsiBStr. The final key to the puzzle is that the string parameter then needs to be passed by reference using the ref keyword.

I can confirm that this works, and that my final working C# code is therefore:

    [DllImport("unmanaged.dll", CharSet = CharSet.Ansi)]
public static extern short DeviceSendRead(
[MarshalAs(UnmanagedType.VBByRefStr)] ref string sCommand,
[MarshalAs(UnmanagedType.VBByRefStr)] ref string sReply,
[MarshalAs(UnmanagedType.VBByRefStr)] ref string sError,
double Timeout);

short err;
string outstr = txtSend.Text;
string readstr = new string(' ', 4000);
string errstr = new string(' ', 100);

err = DeviceSendRead(ref outstr, ref readstr, ref errstr, 10);

I hope this is useful to others facing a similar issue.

pinvoke to function which returns string array

The C++ code increments the pointer like this:

++results;

That increments the address by sizeof(*results) because that is how C++ pointer arithmetic works. So, suppose that sizeof(*results) is equal to 4, as would be the case on a 32 bit machine. Then ++results will increment the address by 4.

Now, your C# code is different. The pointer is untyped and the compiler knows nothing about the underlying array element type. So your code

arrayPtr = new IntPtr(arrayPtr.ToInt64() + 1);

increments the address by 1. Instead you need to supply the missing type information. Like this:

arrayPtr = new IntPtr(arrayPtr.ToInt64() + IntPtr.Size);

On top of that, your loop is implemented incorrectly. You fail to update ptr at the correct time. It should be:

public static IEnumerable<string> MarshalStringArray(IntPtr arrayPtr)
{
if (arrayPtr != IntPtr.Zero)
{
IntPtr ptr = Marshal.ReadIntPtr(arrayPtr);
while (ptr != IntPtr.Zero)
{
string key = Marshal.PtrToStringAnsi(ptr);
yield return key;
arrayPtr = new IntPtr(arrayPtr.ToInt64() + IntPtr.Size);
ptr = Marshal.ReadIntPtr(arrayPtr);
}
}
}

You might prefer to re-cast the method so that the code that it contains only a single call to Marshal.ReadIntPtr.

One final point. The C++ function looks like it might be using the cdecl calling convention. You should check what the definition of SWDLLEXPORT is. Your p/invoke is only correct if SWDLLEXPORT specifies __stdcall.

PInvoke for C function that returns char *

You must return this as an IntPtr. Returning a System.String type from a PInvoke function requires great care. The CLR must transfer the memory from the native representation into the managed one. This is an easy and predictable operation.

The problem though comes with what to do with the native memory that was returned from foo(). The CLR assumes the following two items about a PInvoke function which directly returns the string type

  1. The native memory needs to be freed
  2. The native memory was allocated with CoTaskMemAlloc

Therefore it will marshal the string and then call CoTaskMemFree(...) on the native memory blob. Unless you actually allocated this memory with CoTaskMemAlloc this will at best cause a crash in your application.

In order to get the correct semantics here you must return an IntPtr directly. Then use Marshal.PtrToString* in order to get to a managed String value. You may still need to free the native memory but that will dependent upon the implementation of foo.

C# pinvoke of C function returning char*

You cannot return a pointer to a local variable since those are allocated on the stack (and the stack may get destroyed when the function returns).

You'd need to allocate memory on the heap (preferably using CoTaskMemAlloc so the CLR can free up the memory later) on your C function and return a pointer to that memory.

As a side note, you can directly return a string when marshalling (assuming it's a c null-terminated unicode string), no need to return IntPtr.

More info here: http://limbioliong.wordpress.com/2011/06/16/returning-strings-from-a-c-api/



Related Topics



Leave a reply



Submit