在 Delphi 中使用 C# DLL 仅使用第一个函数参数
Using a C# DLL in Delphi only uses the first function parameter
我使用 C# DLL 导出 (UnmanagedExports - https://www.nuget.org/packages/UnmanagedExports) 使我的托管 C# DLL 可以被非托管代码访问,例如 Delphi。我的问题是只有第一个函数参数从 delphi 传输到 C# dll:
C# DLL 部分
[DllExport("SomeCall", CallingConvention.StdCall)]
public static String SomeCall([MarshalAs(UnmanagedType.LPWStr)] String data1, [MarshalAs(UnmanagedType.LPWStr)] String data2)
{
//Data1 is never filled with some string data.
String result = WorkWithData(data1);
//Data2 is filled with some string data.
result += WorkWithData(data2)
return result;
}
Delphi部分(调用部分):
SomeCall: function(data1: PWideChar; data2: PWideChar;): String StdCall;
procedure DoSomeDLLWork(data1: PWideChar; data2: PWideChar);
var
dllCallResult: String;
begin
dllCallResult := SomeCall(data1,data2);
end
本例的问题是只填充了data2。 data1 永远不会被填满。我已经尝试过 StdCall 和 Cdecl。
编辑:
以下内容有效(data1 和 data2 已正确传输)- return 值从字符串更改为布尔值:
C#(DLL 部分):
[DllExport("SomeCall", CallingConvention.StdCall)]
public static bool SomeCall([MarshalAs(UnmanagedType.LPWStr)] String data1, [MarshalAs(UnmanagedType.LPWStr)] String data2)
Delphi(来电者):
SomeCall: function(data1: PWideChar; data2: PWideChar;): boolean StdCall;
现在我必须考虑 return 值或缓冲区 return 结果字符串返回 delphi。
编辑2:
我接受了 David Heffernan 关于使用 out 参数的建议:
Delphi:
SomeCall: procedure(data1: PWideChar; data2: PWideChar; var result: PWideChar)StdCall;
C#
[DllExport("SomeCall", CallingConvention.StdCall)]
public static bool SomeCall([MarshalAs(UnmanagedType.LPWStr)] String data1, [MarshalAs(UnmanagedType.LPWStr)] String data2, [MarshalAs(UnmanagedType.LPWStr)] out String result)
您必须整理 return 值
像这样:
[DllExport(CallingConvention = CallingConvention.StdCall)]
[return: MarshalAs(UnmanagedType.LPWStr)]
public static string AddCertificate([MarshalAs(UnmanagedType.LPWStr)] string aFileName, [MarshalAs(UnmanagedType.LPWStr)] string aPassword)
不要使用 string
作为结果类型:此类型是 Delphi 私有的。
最简单的方法是使用 BSTR
封送处理,它映射 Delphi WideString
类型。
所以你定义
SomeCall: function(const aFileName, data2: WideString): WideString; StdCall;
可以这样映射:
[DllExport(CallingConvention = CallingConvention.StdCall)]
public static void AddCertificate([MarshalAs(UnmanagedType.BStr)] string data1, [MarshalAs(UnmanagedType.BStr)] string data2, [MarshalAs(UnmanagedType.BStr)] out string Result);
(在答案版之后)技巧是 转换作为附加 out
参数返回的 Delphi 函数 ,如 [=16= 所述].
问题是 string
return 值。在 Delphi 中,string
是托管类型。此外,此类类型的处理方式有些不同寻常。在所有其他参数之后,它们实际上作为额外的隐式 var
参数传递。 C# 代码通过寄存器传递 return 值。
这意味着 C# 函数有 2 个参数,而 Delphi 函数有 3 个参数。这就是解释行为的不匹配。
在任何情况下,return从 C# 中输入一个字符串都会导致一个指向以空字符结尾的字符数组的指针被编组。它当然不会编组为 Delphi 字符串。
您有几个可用的解决方案:
- 保留 C#,将 Delphi return 类型更改为
PAnsiChar
。或者 PWideChar
如果您将 C# return 值编组为 LPWStr
。您需要通过调用 CoTaskMemFree
来释放指针
- 更改 C# 以接受它填充的调用方分配的缓冲区。这将需要
StringBuilder
在 C# 端。并传递缓冲区的长度。
- 更改 C# 以使用
string
类型的输出参数,编组为 UnmanagedType.BStr
。映射到 Delphi 中的 WideString
。
调用者分配缓冲区的问题是要求调用者知道要分配多大的缓冲区。
BStr/WideString
的细微差别是 Delphi 的 ABI 与 Microsoft 的不兼容,请参阅 Why can a WideString not be used as a function return value for interop? 您可以通过 return 将字符串作为out
参数而不是函数 return 值。
返回 C# string
,编组为 LPWStr
,映射到 PWideChar
,让您完成调用 CoTaskMemFree
释放内存的任务。总的来说,我认为我会 select 这个选项。这是该方法的示例。
C#
using System.Runtime.InteropServices;
using RGiesecke.DllExport;
namespace ClassLibrary1
{
public class Class1
{
[DllExport]
[return: MarshalAs(UnmanagedType.LPWStr)]
public static string Concatenate(
[MarshalAs(UnmanagedType.LPWStr)] string str1,
[MarshalAs(UnmanagedType.LPWStr)] string str2
)
{
return str1 + str2;
}
}
}
Delphi
{$APPTYPE CONSOLE}
uses
Winapi.ActiveX; // for CoTaskMemFree
const
dllname = 'ClassLibrary1.dll';
function Concatenate(str1, str2: PWideChar): PWideChar; stdcall; external dllname;
procedure Main;
var
res: PWideChar;
str: string;
begin
res := Concatenate('foo', 'bar');
str := res;
CoTaskMemFree(res);
Writeln(Str);
end;
begin
Main;
Readln;
end.
输出
foobar
我使用 C# DLL 导出 (UnmanagedExports - https://www.nuget.org/packages/UnmanagedExports) 使我的托管 C# DLL 可以被非托管代码访问,例如 Delphi。我的问题是只有第一个函数参数从 delphi 传输到 C# dll:
C# DLL 部分
[DllExport("SomeCall", CallingConvention.StdCall)]
public static String SomeCall([MarshalAs(UnmanagedType.LPWStr)] String data1, [MarshalAs(UnmanagedType.LPWStr)] String data2)
{
//Data1 is never filled with some string data.
String result = WorkWithData(data1);
//Data2 is filled with some string data.
result += WorkWithData(data2)
return result;
}
Delphi部分(调用部分):
SomeCall: function(data1: PWideChar; data2: PWideChar;): String StdCall;
procedure DoSomeDLLWork(data1: PWideChar; data2: PWideChar);
var
dllCallResult: String;
begin
dllCallResult := SomeCall(data1,data2);
end
本例的问题是只填充了data2。 data1 永远不会被填满。我已经尝试过 StdCall 和 Cdecl。
编辑:
以下内容有效(data1 和 data2 已正确传输)- return 值从字符串更改为布尔值:
C#(DLL 部分):
[DllExport("SomeCall", CallingConvention.StdCall)]
public static bool SomeCall([MarshalAs(UnmanagedType.LPWStr)] String data1, [MarshalAs(UnmanagedType.LPWStr)] String data2)
Delphi(来电者):
SomeCall: function(data1: PWideChar; data2: PWideChar;): boolean StdCall;
现在我必须考虑 return 值或缓冲区 return 结果字符串返回 delphi。
编辑2:
我接受了 David Heffernan 关于使用 out 参数的建议:
Delphi:
SomeCall: procedure(data1: PWideChar; data2: PWideChar; var result: PWideChar)StdCall;
C#
[DllExport("SomeCall", CallingConvention.StdCall)]
public static bool SomeCall([MarshalAs(UnmanagedType.LPWStr)] String data1, [MarshalAs(UnmanagedType.LPWStr)] String data2, [MarshalAs(UnmanagedType.LPWStr)] out String result)
您必须整理 return 值
像这样:
[DllExport(CallingConvention = CallingConvention.StdCall)]
[return: MarshalAs(UnmanagedType.LPWStr)]
public static string AddCertificate([MarshalAs(UnmanagedType.LPWStr)] string aFileName, [MarshalAs(UnmanagedType.LPWStr)] string aPassword)
不要使用 string
作为结果类型:此类型是 Delphi 私有的。
最简单的方法是使用 BSTR
封送处理,它映射 Delphi WideString
类型。
所以你定义
SomeCall: function(const aFileName, data2: WideString): WideString; StdCall;
可以这样映射:
[DllExport(CallingConvention = CallingConvention.StdCall)]
public static void AddCertificate([MarshalAs(UnmanagedType.BStr)] string data1, [MarshalAs(UnmanagedType.BStr)] string data2, [MarshalAs(UnmanagedType.BStr)] out string Result);
(在答案版之后)技巧是 转换作为附加 out
参数返回的 Delphi 函数 ,如 [=16= 所述].
问题是 string
return 值。在 Delphi 中,string
是托管类型。此外,此类类型的处理方式有些不同寻常。在所有其他参数之后,它们实际上作为额外的隐式 var
参数传递。 C# 代码通过寄存器传递 return 值。
这意味着 C# 函数有 2 个参数,而 Delphi 函数有 3 个参数。这就是解释行为的不匹配。
在任何情况下,return从 C# 中输入一个字符串都会导致一个指向以空字符结尾的字符数组的指针被编组。它当然不会编组为 Delphi 字符串。
您有几个可用的解决方案:
- 保留 C#,将 Delphi return 类型更改为
PAnsiChar
。或者PWideChar
如果您将 C# return 值编组为LPWStr
。您需要通过调用CoTaskMemFree
来释放指针
- 更改 C# 以接受它填充的调用方分配的缓冲区。这将需要
StringBuilder
在 C# 端。并传递缓冲区的长度。 - 更改 C# 以使用
string
类型的输出参数,编组为UnmanagedType.BStr
。映射到 Delphi 中的WideString
。
调用者分配缓冲区的问题是要求调用者知道要分配多大的缓冲区。
BStr/WideString
的细微差别是 Delphi 的 ABI 与 Microsoft 的不兼容,请参阅 Why can a WideString not be used as a function return value for interop? 您可以通过 return 将字符串作为out
参数而不是函数 return 值。
返回 C# string
,编组为 LPWStr
,映射到 PWideChar
,让您完成调用 CoTaskMemFree
释放内存的任务。总的来说,我认为我会 select 这个选项。这是该方法的示例。
C#
using System.Runtime.InteropServices;
using RGiesecke.DllExport;
namespace ClassLibrary1
{
public class Class1
{
[DllExport]
[return: MarshalAs(UnmanagedType.LPWStr)]
public static string Concatenate(
[MarshalAs(UnmanagedType.LPWStr)] string str1,
[MarshalAs(UnmanagedType.LPWStr)] string str2
)
{
return str1 + str2;
}
}
}
Delphi
{$APPTYPE CONSOLE}
uses
Winapi.ActiveX; // for CoTaskMemFree
const
dllname = 'ClassLibrary1.dll';
function Concatenate(str1, str2: PWideChar): PWideChar; stdcall; external dllname;
procedure Main;
var
res: PWideChar;
str: string;
begin
res := Concatenate('foo', 'bar');
str := res;
CoTaskMemFree(res);
Writeln(Str);
end;
begin
Main;
Readln;
end.
输出
foobar