在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:
I use C# DLL Export (UnmanagedExports - https://www.nuget.org/packages/UnmanagedExports) to make my managed C# DLL Accessible to unmanged Code like Delphi. My problem is that only first function parameter is transfered from delphi to the 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部分(调用部分):
The 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。
The problem in this case is that only data2 is filled. data1 is never filled. I already tried StdCall and Cdecl.
编辑:
以下内容有效(data1和data2正确传输)-返回值从字符串更改为布尔值:
The following thing works (data1 and data2 ist transfered correctly) - return value changed from string to boolean:
C#(DLL部分):
C# (DLL Part):
[DllExport("SomeCall", CallingConvention.StdCall)]
public static bool SomeCall([MarshalAs(UnmanagedType.LPWStr)] String data1, [MarshalAs(UnmanagedType.LPWStr)] String data2)
Delphi(呼叫者):
Delphi (Caller):
SomeCall: function(data1: PWideChar; data2: PWideChar;): boolean StdCall;
现在我必须考虑一个返回值或一个缓冲区,以将结果字符串返回给delphi。
Now I have to think about a return value or a a buffer to return the result string back to delphi.
Edit2 :
我同意David Heffernan建议使用out参数:
I went with David Heffernan's suggestion of using an out parameter:
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)
推荐答案
问题是 string
返回值。在Delphi中, string
是托管类型。此外,对这些类型进行了一些不寻常的处理。实际上,它们在所有其他参数之后作为额外的隐式 var
参数传递。 C#代码通过寄存器传递返回值。
The problem is the string
return value. In Delphi a string
is a managed type. Furthermore, such types are given somewhat unusual treatment. They are actually passed as an extra implicit var
parameter, after all other parameters. The C# code passes the return value through a register.
这意味着C#函数具有2个参数,而Delphi函数具有3个参数。那就是不匹配的原因。
What this means is that the C# function has 2 paramaters but the Delphi function has 3 parameters. That's the mismatch that explains the behaviour.
在任何情况下,从C#返回字符串都会导致指向以null结尾的字符数组的指针被编组。它当然不会作为Delphi字符串进行封送。
In any case returning a string from C# results in a pointer to null terminated array of characters being marshalled. It certainly does not marshal as a Delphi string.
您有一些可用的解决方案:
You've got a few solutions available:
- 不使用C#并将Delphi返回类型更改为
PAnsiChar
。或者,如果将C#返回值编组为LPWStr
或PWideChar
。您需要通过调用CoTaskMemFree
- 释放C#以接受由它填充的调用方分配的缓冲区。这将需要在C#端使用
StringBuilder
。并传递缓冲区的长度。 - 更改C#以使用类型为
string
的out参数,将其编组为UnmanagedType.BStr
。这对应于Delphi中的WideString
。
- Leave the C# alone and change the Delphi return type to
PAnsiChar
. OrPWideChar
if you marshal the C# return value asLPWStr
. You'll need to free the pointer by callingCoTaskMemFree
- Change the C# to accept a caller allocated buffer which it populates. That would require
StringBuilder
on the C# side. And passing the length of the buffer. - Change the C# to use an out parameter of type
string
, marshalled asUnmanagedType.BStr
. That maps toWideString
in Delphi.
调用方分配的缓冲区的问题在于,要求调用方知道要分配多少缓冲区。
The problem with caller allocated buffer is that requires the caller to know how large a buffer to allocate.
与 BStr / WideString
的细微差别在于Delphi的ABI与Microsoft的ABI不兼容,请参见为什么不能将WideString用作互操作的函数返回值?您可以通过将字符串作为返回来解决此问题。 out
参数而不是函数返回值。
The nuance with BStr/WideString
is that Delphi's ABI is not compatible with Microsoft's, see Why can a WideString not be used as a function return value for interop? You can work around this by returning the string as an out
parameter rather than the function return value.
返回C#字符串
,编组为 LPWStr
,映射到 PWideChar
,使您可以调用 CoTaskMemFree
释放内存。总的来说,我认为我会选择此选项。这是该方法的示例。
Returning a C# string
, marshalled as LPWStr
, mapped to PWideChar
, leaves you with the task of calling CoTaskMemFree
to free the memory. On balance I think I'd select this option. Here is an example of that approach.
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
这篇关于在Delphi中使用C#DLL仅使用第一个函数参数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!