我有一个 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 之间,最简单的方法是使用
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 堆。
我尝试了所有其他答案,但让它像这样工作:
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);
}
这需要比我想要的更多的管理,但否则无法让它工作。