4

// delphi code (delphi version : Turbo Delphi Explorer (it's Delphi 2006))

function GetLoginResult:PChar;
   begin
    result:=PChar(LoginResult);
   end; 

//C# code to use above delphi function (I am using unity3d, within, C#)

[DllImport ("ServerTool")]
private static extern string GetLoginResult();  // this does not work (make crash unity editor)

[DllImport ("ServerTool")] 
[MarshalAs(UnmanagedType.LPStr)] private static extern string GetLoginResult(); // this also occur errors

What is right way to use that function in C#?

(for use in also in delphi, code is like, if (event=1) and (tag=10) then writeln('Login result: ',GetLoginResult); )

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
creator
  • 671
  • 11
  • 28

1 Answers1

8

The memory for the string is owned by your Delphi code but your p/invoke code will result in the marshaller calling CoTaskMemFree on that memory.

What you need to do is to tell the marshaller that it should not take responsibility for freeing the memory.

[DllImport ("ServerTool")] 
private static extern IntPtr GetLoginResult();

Then use Marshal.PtrToStringAnsi() to convert the returned value to a C# string.

IntPtr str = GetLoginResult();
string loginResult = Marshal.PtrToStringAnsi(str);

You should also make sure that the calling conventions match by declaring the Delphi function to be stdcall:

function GetLoginResult: PChar; stdcall;

Although it so happens that this calling convention mis-match doesn't matter for a function that has no parameters and a pointer sized return value.

In order for all this to work, the Delphi string variable LoginResult has to be a global variable so that its contents are valid after GetLoginResult returns.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Would the calling convention matter in this case as well? – Anya Shenanigans Jun 24 '12 at 09:41
  • @Petesh in fact no because the function has no params and return value is handled the same for stdcall and register. But it would be better to declare Delphi function as stdcall. Thanks for that. – David Heffernan Jun 24 '12 at 11:21
  • I am pretty sure that if you define the return value of your DllImport function as string the marshaller handles it correctly as described in the msdn (http://msdn.microsoft.com/en-us/library/e765dyyy.aspx). – Stefan Glienke Jun 24 '12 at 12:28
  • @Stefan What happens is that the marshaller calls `CoTaskMemFree` on the pointer which you return. – David Heffernan Jun 24 '12 at 12:44
  • @DavidHeffernan Thanks for reply, but this occur errors... See this error pictures, – creator Jun 24 '12 at 12:56
  • http://i1105.photobucket.com/albums/h355/leegod/error2.png http://i1105.photobucket.com/albums/h355/leegod/error3.png – creator Jun 24 '12 at 12:58
  • Please read the first error message. You can't do this in a field initializer. You have to put the code inside a method. – David Heffernan Jun 24 '12 at 13:06
  • @DavidHeffernan Wow, yes you are right, it works! Thanks man. But how you could know all this? By reading book and forum threads? – creator Jun 24 '12 at 14:15
  • @Stefan In order to understand that MSDN sample, you need to look at the [unmanaged code that is on the other side of the interface](http://msdn.microsoft.com/en-us/library/as6wyhwt). As you can see, that calls `CoTaskMemAlloc` to allocate the string, just as I said. – David Heffernan Jun 25 '12 at 08:46
  • @David Sorry, I forgot the fact that you have to use WideString in your delphi dll in that case as mentioned in [another SO question](http://stackoverflow.com/questions/2273141/how-can-i-pass-a-delphi-string-to-a-prism-dll). But to me that's the best way to do it. – Stefan Glienke Jun 25 '12 at 10:15
  • @StefanGlienke A BSTR/WideString is not quite the same. Note also that we are dealing with 8 bit char here since that's the default for p/invoke. Even if it was 16-bit char, a BSTR has extra payload. And even allowing for all of that, returning a `WideString` from a Delphi DLL is not binary compatible with other compilers. See my question on that topic: http://stackoverflow.com/questions/9349530/why-can-a-widestring-not-be-used-as-a-function-return-value-for-interop – David Heffernan Jun 25 '12 at 10:47
  • @David First they are not binary incompatible, it seems to be more of a calling convention problem. Second the problem you describe seems to be when doing interop between c++ and Delphi. We are talking about C# and Delphi which uses the marshaller. Haven't seen any problem in that case yet and it's the suggested way whenever I read about it. – Stefan Glienke Jun 25 '12 at 11:30
  • @StefanGlienke If the calling conventions don't match, that is a binary incompatibility. Calling convention is part of the ABI. And the odd one out in all of this is Delphi. C++ and C# handle BSTR return values the same way. Delphi treats them differently. If you actually try to write a Delphi DLL that returns a `WideString`, and then call it with p/invoke, you will see what I am talking about. – David Heffernan Jun 25 '12 at 11:41
  • @David I did not just try, I actually did it some while ago and it worked fine – Stefan Glienke Jun 25 '12 at 11:42
  • @StefanGlienke In fact a Delphi function which returns a `WideString` cannot be called from a C# p/invoke using `[return: MarshalAs(UnmanagedType.BStr)]`. I guess we must be talking at cross-purposes. You must be doing something different from me. I updated that other question to bring out the point that Delphi is the black sheep of the family. – David Heffernan Jun 25 '12 at 12:06