I am using PInvoke for interoperability between Native Code (C++) and Managed Code (C#). I just write a simple function which gets a string from C++ code. My code looks like
C# Code:
[DllImport("MyDll.dll")]
private static extern string GetSomeText();
public static string GetAllValidProjects() {
string s = GetSomeText();
return s;
}
C++ Code
char* GetSomeText() {
std::string stri= "Some Text Here";
char * pchr = (char *)stri.c_str();
return pchr;
}
All works fine at C++ end, i.e the variable pchr
contains "Some Text Here" but at C# the string s
contains noting in it. I don't know what I am doing wrong. Any help would be appreciated
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.
Another way to get a string from C++. In case the you cannot modify your c++ dll. You can declare the DllImport with IntPtr instead of string. When the function is invoked, you can marshal the Ptr back to String.
[DllImport("MyDll.dll")]
private static extern IntPtr GetSomeText();
public static string GetAllValidProjects()
{
string s = Marshal.PtrToStringAnsi(GetSomeText());
return s;
}
Note : as mention in the previous post. "Some Text Here" is allocated on the stack so as soon as the function returns, the stack will unwire. Therefore the data is potential be overridden. Hence you shall use Marshal.PtrToStringAnsi right after the call. Don't hold to the IntPtr.
char* GetSomeText()
{
std::string stri= "Some Text Here";
char * pchr = (char *)stri.c_str();
return pchr;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With