7

I am trying to compile a 64bit dll for use with a 64bit C# application. I have a simple class and a simple app to try and test it and it falls over no matter what I try and do. Here is the code:

Delphi

library project1;

{$mode objfpc}{$H+}

uses
  Classes;


function Encrypt(aName:PChar):PChar;stdcall;
begin
  Result := aName;
end;


exports Encrypt;

begin
end.

C#

 [DllImport("project1.dll")]
    [return: MarshalAs(UnmanagedType.LPStr)]
    public static extern String Encrypt([MarshalAs(UnmanagedType.LPStr)] String aName);

Can anyone see anything wrong with it and if not fancy creating the same simple scenario to try and get this to work, I'm at the end of my tether!

Jon
  • 38,814
  • 81
  • 233
  • 382

1 Answers1

11

The problem with this is that the C# marshaller passes a temporary block of memory into the function as aName. This memory is the destroyed when the function returns. But you are also asking the C# marshaller to marshal this same block of memory into a C# string.

It's not good practice to return a null-terminated string from a native DLL function anyway. You have a couple of options:

  1. Use a StringBuilder on the C# side to pre-allocate the memory for the string. This requires you to get hold of the required size somehow. This is the most common way to interop strings.
  2. Return the string as a COM BSTR and the C# marshaller knows how to marshall and dispose a BSTR, and has access to the COM allocator to do so. I have no knowledge about using BSTR in FreePascal but in Delphi you simply use WideString. You also need to tell the C# marshaller that you are returning a BSTR.

I personally have a preference for option 2. There is one wrinkle though and that is that different compilers use a different ABI for function return values, as discussed at this question: Why can a WideString not be used as a function return value for interop? The easy way around that is to return the string in a parameter rather than using the function return value.

The code looks like this:

Pascal

procedure Encrypt(Input: WideString; out Output: WideString); stdcall;
begin
  Output := Input;
end;

C#

[DllImport("project1.dll")]
public static extern void Encrypt(
    [MarshalAs(UnmanagedType.BStr)] string input;
    [MarshalAs(UnmanagedType.BStr)] out string output
);
Community
  • 1
  • 1
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • @Jon That's what you'd do in Delphi (`BSTR` == `WideString`) but I don't know if it works the same in FP. If you can't do it that way in FP then it's probably easy enough to call `SysAllocString`, but you'd need to get UTF-16 text. Have a look at this answer to see how: http://stackoverflow.com/questions/5308584/how-to-return-text-from-native-c-code – David Heffernan Jun 13 '11 at 19:36
  • I just changed PChar to Widestring and built the DLL fine however the C# app still doesnt like it – Jon Jun 13 '11 at 19:38
  • On the C# side you need to use `MarshalAs(UnmanagedType.BStr)`. – David Heffernan Jun 13 '11 at 19:40
  • Actually, looking at a couple of blog post seems to suggest that WideString is well supported in FP. – David Heffernan Jun 13 '11 at 19:41