我使用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 Part(Calling part):
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正确传输) - 返回值从字符串更改为布尔值:
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。
EDIT2:
我和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)
答案 0 :(得分:3)
不要将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函数,如Delphi low-level information of parameter marshalling所述。
答案 1 :(得分:3)
问题是string
返回值。在Delphi中,string
是托管类型。此外,这些类型的处理有些不同寻常。它们实际上是在所有其他参数之后作为额外的隐式var
参数传递的。 C#代码通过寄存器传递返回值。
这意味着C#函数有2个参数,但Delphi函数有3个参数。这是解释行为的不匹配。
在任何情况下,从C#返回一个字符串会导致指向空终止的字符数组的指针被编组。它当然不会作为Delphi字符串编组。
您可以获得一些解决方案:
PAnsiChar
。如果您将C#返回值编组为PWideChar
,请LPWStr
。您需要通过调用CoTaskMemFree
StringBuilder
。并传递缓冲区的长度。 string
的out参数,编组为UnmanagedType.BStr
。这映射到Delphi中的WideString
。 调用者分配缓冲区的问题是需要调用者知道要分配的缓冲区大小。
BStr/WideString
的细微差别在于Delphi的ABI与Microsoft不兼容,请参阅Why can a WideString not be used as a function return value for interop?您可以通过将字符串作为{{1}返回来解决此问题参数而不是函数返回值。
返回C#out
,编组为string
,映射到LPWStr
,让您完成调用PWideChar
以释放内存的任务。总的来说,我认为我选择了这个选项。以下是该方法的一个示例。
<强> C#强>
CoTaskMemFree
<强>的Delphi 强>
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;
}
}
}
<强>输出强>
foobar
答案 2 :(得分:0)
你必须编组返回值
像这样:
[DllExport(CallingConvention = CallingConvention.StdCall)]
[return: MarshalAs(UnmanagedType.LPWStr)]
public static string AddCertificate([MarshalAs(UnmanagedType.LPWStr)] string aFileName, [MarshalAs(UnmanagedType.LPWStr)] string aPassword)