从 Delphi dll 返回字符串到 C#

问题描述 投票:0回答:2

我有一个 delphi dll,我想将字符串返回到 C# API。

我正在使用 Delphi 11 和 .NET 7

我之前问过类似的问题,我开始工作,向delphi发送一个缓冲区,该缓冲区被填充并作为

out
参数返回。

现在我需要该函数返回一个未知长度的字符串,并且我正在努力让它工作。

这是一个显示错误的小示例:

德尔福:

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#:(特定的

DllImport
属性可能看起来是随机的,因为我已经尝试了所有组合)

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

有人可以指出我正确的方向吗?

c# delphi interop
2个回答
0
投票

在 C# 和 Delphi 之间,最简单的方法是使用

WideString/BSTR
来实现这样的库。

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

WideString/BSTR
的好处是双方都知道它们是本机类型,因此C#和Delphi都会正确管理它们的内存。而且它们也是 UTF-16 编码的,因此它非常适合也使用 UTF-16 编码的 Delphi 11。

当然,

WideString
UnicodeString
之间会有转换,但这不会有什么坏处,而且速度足够快,尤其是在现代Windows上。无论如何,记忆都会得到妥善处理:安全总比后悔好。

但不要忘记在 Win32 上使用

stdcall
调用约定 - 在 Win64 上不需要。

也许可以在参数中添加一个

const
,这样实现函数中就不会存在任何本地副本。不需要双重内存分配,但没有任何好处。

最后但并非最不重要的一点是,您不需要

sharemem
单元,因为您依赖于
WideString/BSTR
,它使用 Windows 堆,而不是 Delphi 堆。


0
投票

我尝试了所有其他答案,但让它像这样工作:

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

procedure DisposeStr(text: PWideChar);
begin
  StrDispose(text);
end;

C# 调用:

[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);
}

这需要比我想要的更多的管理,但否则无法让它工作。

© www.soinside.com 2019 - 2024. All rights reserved.