McArthor Lee
McArthor Lee

Reputation: 317

C++ dll returned string is corrupted in the C# caller, why?

I have a C# app that calls a C++ DLL.

In C#, I have code as follows:

[DllImport(@"111.dll", CharSet = CharSet.Unicode)]
public extern static String Func1(String arg);
......
String arg = "test text";
String retstring = Func1(arg);

In CPP, I have the function defined as follows:

extern "C"
{
__declspec(dllexport) LPWSTR Func1(LPWSTR arg)
{
          ....
          LPWSTR ret1 = L"1?2?3?4?5";
          LPWSTR ret2 = SomeActualFunction(arg);
          retturn ret1; // return ret2;
     }
}

If I return ret1 in C++'s Func1(), all works fine. And in VS2008's memory window, I can see correct Unicode binaries. In C++ the binaries of ret1 is

"31 00 3f 00 32 00 3f 00 33 00 3f 00 34 00 3f 00 35 00"

, and in C# the binaries of retstring is

"28 67 a3 f7 fe 07 00 00 0a 00 00 00 09 00 00 00 31 00 3f 00 32 00 3f 00 33 00 3f 00 34 00 3f 00 35 00"

. I think the C# binaries

"28 67 a3 f7 fe 07 00 00 0a 00 00 00 09 00 00 00"

are the header of System.String type.

What's more, if I add the following line just before return in CPP code, I can get correct retstring in C# code too:

ret2 = L"1?2?3?4?5";

But when I return ret2 in the C++ DLL, the returned string ret in C# seems to be corrupted. The binaries in the C++ DLL are correct Unicode on my inspection. But the binaries of retstring in the C# code are

"28 67 a3 f7 fe 07 00 00 0a 00 00 00 09 00 00 00 dd dd dd dd dd dd dd dd dd dd dd dd ....".

I can only notice ret2 is longer than ret1 - ret2 has some hundreds of WCHARs.

Any ideas? Thx in advance.

Upvotes: 6

Views: 2901

Answers (7)

cowboydan
cowboydan

Reputation: 1102

Here is a link for your reference should you need to marshal other data types UPDATED LINK FOR .NET 4.0 https://msdn.microsoft.com/en-us/library/sak564ww(v=vs.100).aspx

Upvotes: 0

Indinfer
Indinfer

Reputation: 923

I think I got this working: A C# call to a C++ DLL, returns a string to C#. I will share what I think are the things that make it work:

In C#

using System.Runtime.InteropServices;

[DllImport(
    @"C:\Users\Ron\Documents\Visual Studio 2013\ etc. ...(The whole path)... my.dll,
    CallingConvention = CallingConvention.Cdecl
    )]
[return: MarshalAs(UnmanagedType.BStr)]
public static extern string cpp_brand_files(string home_dir, string xml_lic);

And the call later in C#:

      string a_str = cpp_brand_files(home_dir, xml_license);

In C++ DLL:

  #using <mscorlib.dll>

   using namespace System;

   extern "C" __declspec(dllexport)
   wchar_t* cpp_brand_files(char* home_dir, char* xml_lic);

And further down in C++:

  wchar_t* cpp_brand_files(char* home_dir, char* xml_lic)
  {

      BSTR samp = ::SysAllocString( L"This is the long, long string." );
      return samp;
  }

AND ALSO, compile the C++ DLL with /cli (Common Language Runtime Support) in the configuration properties. Right click the C++ project, then Properties -> Configuration Properties -> General -> Common Language Runtime Support.

So that's what I think are the bare bones of what makes it work (returning a string).

I am using Visual Studio 2013 RC. Experts, please comment on what is extra or missing.

Upvotes: 0

Dan
Dan

Reputation: 1959

I've uploaded a sample code on SkyDrive (http://cid-48a119f5ed65483e.office.live.com/self.aspx/.Public/MarshalString.zip). It demonstrates how to pass a managed string to unmanaged code and how to manipulate it.

Upvotes: 0

David Heffernan
David Heffernan

Reputation: 613501

I would always use a BSTR for this because it makes the responsibility of memory allocation/deallocation transparent.

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();

You don't say how your strings are being allocated, but as soon as you use a dynamically allocated string you need to tackle that issue. The great thing about BSTR is that it uses the shared COM allocator which enables the C# marshaller to deallocate the string with the same allocator as the C++ code that allocated it.

Upvotes: 8

Mark Tolonen
Mark Tolonen

Reputation: 178179

Is your func __cdecl or __stdcall calling convention? IIRC, the default is for C# is __stdcall, but the default for C++ is __cdecl. Try adding CallingConvention=CallingConvention.Cdecl.


Another possibility: Since you say returning a static string works, is the pointer returned by SomeActualFunction still valid? If it pointed to a local buffer in that function, it would no longer be valid after returning from the function.

Upvotes: 1

Alex F
Alex F

Reputation: 43331

So, if you return the pointer to hard-coded string L"1?2?3?4?5", everything is OK. But if you return SomeActualFunction, the answer is incorrect. Maybe C++ code is incorrect? How does SomeActualFunction work? For example, it may return LPWSTR from some stack-allocated object which is destroyed at this time. Try to test this with C++ client first.

Upvotes: 0

Waleed
Waleed

Reputation: 3145

[DllImport(@"111.dll", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.LPStr)]
public extern static String Func1(String arg);

or

[DllImport(@"111.dll", CharSet = CharSet.Unicode)]
public extern static IntPtr Func1(String arg);
// In your calling code
string result = Marshal.PtrToStringUni(Func1("somestring"));

Upvotes: 5

Related Questions