Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Returning a string from delphi dll to C# caller in 64 bit

Tags:

c#

pinvoke

delphi

I have a C# application which calls native Delphi dll using the following code:

C#

[DllImport("NativeDLL.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
public static extern int GetString(string out str);

Delphi

function GetString(out a: PChar): Integer; stdcall;
begin
  a := PChar('abc');
  Result := 1;
end;

which works fine in a 32 bit application. But when I compile both C# exe and Delphi dll for 64 bit I get a strange problem. After a call to GetString in Delphi debugger I can see that an exception is raised somewhere in the .NET code and the following string appears in the Debugger Output window: "Critical error detected c0000374". Google says that this error is related to heap corruption. I tried using ref/var parameters modifiers instead of out/out. Still no luck. Why do I get this error? Should I use a different calling convention for 64 bit?

BTW. The following combination works fine:

C#

[DllImport("NativeDLL.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
public static extern string GetString(string a);

Delphi

function GetString(a: PChar): PChar; stdcall;
var
  inp: string;
begin
  inp := a;
  Result := PChar('test ' + inp);
end;

works fine. But I do need to return a string as an out parameter.

like image 944
Max Avatar asked Apr 23 '13 13:04

Max


1 Answers

You cannot pass a string from native managed that way. Your code is wrong in 32 bit also, you just happen to get away with it. The second version of the code is also wrong. It only appears to work.

You need to either:

  1. Allocate from a shared heap so that that the managed code can deallocate off that heap. The shared heap for p/invoke is the COM heap.
  2. Allocate the memory on the managed side, and copy the contents into that buffer on the native side.

Option 2 is always preferable. It looks like this:

[DllImport("NativeDLL.dll", CharSet = CharSet.Unicode)]
public static extern int GetString(StringBuilder str, int len);

On the native side you would have

function GetString(str: PChar; len: Integer): Integer; stdcall;
begin
  StrLCopy(str, 'abc', len);
  Result := 1; // real code would have real error handling
end;

Then call this like so:

StringBuilder str = new StringBuilder(256);
int retval = GetString(str, str.Capacity);

If you want to try option 1, it looks like this on the managed side:

[DllImport("NativeDLL.dll", CharSet = CharSet.Unicode)]
public static extern int GetString(out string str);

and like this native:

function GetString(out str: PChar): Integer; stdcall;
begin
  str = CoTaskMemAlloc(SizeOf(Char)*(Length('abc')+1));
  StrCopy(str, 'abc');
  Result := 1; // real code would have real error handling
end;

When the managed code copies the contents of str to the string value, it then calls CoTaskMemFree on the pointer that you returned.

And this is trivially easy to call:

string str;
int retval = GetString(out str);
like image 200
David Heffernan Avatar answered Oct 17 '22 00:10

David Heffernan