我使用 C# DLL Export (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 已正确传输)- 返回值从字符串更改为 bool 值:
C#(DLL 部分):
[DllExport("SomeCall", CallingConvention.StdCall)]
public static bool SomeCall([MarshalAs(UnmanagedType.LPWStr)] String data1, [MarshalAs(UnmanagedType.LPWStr)] String data2)
德尔福(调用方):
SomeCall: function(data1: PWideChar; data2: PWideChar;): boolean StdCall;
现在我必须考虑一个返回值或一个缓冲区来将结果字符串返回给delphi。
编辑2:
我接受了 David Heffernan 关于使用 out 参数的建议:
德尔福:
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)
最佳答案
问题是 string
返回值。在 Delphi 中 string
是托管类型。此外,此类类型的处理方式有些不同寻常。它们实际上作为额外的隐式传递 var
参数,在所有其他参数之后。 C# 代码通过寄存器传递返回值。
这意味着 C# 函数有 2 个参数,而 Delphi 函数有 3 个参数。这就是解释行为的不匹配。
在任何情况下,从 C# 返回一个字符串都会导致一个指向以 null 结尾的字符数组的指针被编码。它肯定不会编码为 Delphi 字符串。
您有一些可用的解决方案:
- 保留 C#,将 Delphi 返回类型更改为
PAnsiChar
.或者PWideChar
如果将 C# 返回值编码为LPWStr
.您需要通过调用CoTaskMemFree
来释放指针 - 更改 C# 以接受它填充的调用者分配的缓冲区。那将需要
StringBuilder
在 C# 方面。并传递缓冲区的长度。 - 更改 C# 以使用
string
类型的输出参数, 编码为UnmanagedType.BStr
.映射到WideString
在德尔福。
调用者分配缓冲区的问题是要求调用者知道要分配多大的缓冲区。
BStr/WideString
的细微差别是 Delphi 的 ABI 与 Microsoft 的不兼容,参见 Why can a WideString not be used as a function return value for interop?您可以通过将字符串作为 out
返回来解决此问题参数而不是函数返回值。
返回 C# string
, 编码为 LPWStr
, 映射到 PWideChar
, 让您完成调用 CoTaskMemFree
的任务释放内存。总的来说,我想我会选择这个选项。这是该方法的示例。
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;
}
}
}
德尔福
{$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# - 在 Delphi 中使用 C# DLL 只使用第一个函数参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33252249/