Search code examples
c#delphiinterop

Returning strings from Delphi dll to C#


I have a delphi dll I want to return strings to a C# API.

I am using Delphi 11, and .NET 7

I have asked a similar question before, which I got to work, with sending a buffer to delphi which got filled and returned as an out param.

Now I need the function to return a string of unknown length, and I am struggling getting it to work.

Here is a small sample that shows the errors:

Delphi:

library Project1;
uses
  Sharemem;

{$R *.res}

function TestWide(aInput: WideString): WideString;
begin
  result := aInput;
end;

function TestPChar(aInput: PChar): PChar;
begin
  result := aInput;
end;

function TestString(aInput: string): string;
begin
  result := aInput;
end;

exports
  TestWide, TestPChar, TestString;

begin
end.

C#: (The specific DllImport attributes might look random because I've tried all combinations)

using System.Runtime.InteropServices;

internal class Program
{
        [DllImport("Project1.dll", EntryPoint = "TestWide", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, BestFitMapping = true)]
        [return: MarshalAs(UnmanagedType.BStr)]
        public static extern string TestWide([MarshalAs(UnmanagedType.BStr)] string input);

        [DllImport("Project1.dll", EntryPoint = "TestPChar", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
        [return: MarshalAs(UnmanagedType.LPStr)]
        public static extern string TestPChar([MarshalAs(UnmanagedType.LPStr)] string input);

        [DllImport("Project1.dll", EntryPoint = "TestString", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)]
        [return: MarshalAs(UnmanagedType.BStr)]
        public static extern string TestString([MarshalAs(UnmanagedType.BStr)] string input);

    private static void Main(string[] args)
    {
        while (true)
        {
            Console.WriteLine("Press key to test...");
            Console.WriteLine("W for WideString");
            Console.WriteLine("P for PChar");
            Console.WriteLine("S for String");

            var key = Console.ReadKey().KeyChar;
            try
            {
                switch (key)
                {
                    case 'w':
                        Console.WriteLine(TestWide("Wide")); //Runtime error 203 at address
                        break;
                    case 'p':
                        Console.WriteLine(TestPChar("Pchar")); //Another -big number exit code
                        break;
                    case 's':
                        TestString("str");  //System.AccessViolation
                        break;
                    default:
                        break;
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }
    }
}

Can someone please point me in the correct direction.


Solution

  • I tried all other answers but got it to work like this:

    function TestWide(text: PWideChar): PWideChar
    begin
      //DoStuff
    end;
    
    procedure DisposeStr(text: PWideChar);
    begin
      StrDispose(text);
    end;
    

    C# call:

    [LibraryImport("MyDll.dll", EntryPoint = "TestWide", StringMarshalling = StringMarshalling.Utf16)]
    [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvStdcall) })]
    private static partial IntPtr TestWide(string parameters);
    
    [LibraryImport("MyDll.dll", EntryPoint = "DisposeStr")]
    [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvStdcall) })]
    private static partial void DisposeStr(IntPtr str);
    
    public static void Test()
    {
            IntPtr widePtr = TestWide("TextMessage");
            string? wideStr = Marshal.PtrToStringUni(widePtr);
            DisposeStr(widePtr);
    }
    

    It takes a little more management than I would like, but could not get it to work otherwise.