在Delphi中使用C#DLL只使用第一个函数参数

时间:2015-10-21 06:18:53

标签: c# delphi dll delphi-xe2 dllexport

我使用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)

3 个答案:

答案 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字符串编组。

您可以获得一些解决方案:

  1. 单独保留C#并将Delphi返回类型更改为PAnsiChar。如果您将C#返回值编组为PWideChar,请LPWStr。您需要通过调用CoTaskMemFree
  2. 来释放指针
  3. 更改C#以接受它填充的调用者分配的缓冲区。这需要C#端的StringBuilder。并传递缓冲区的长度。
  4. 更改C#以使用类型为string的out参数,编组为UnmanagedType.BStr。这映射到Delphi中的WideString
  5. 调用者分配缓冲区的问题是需要调用者知道要分配的缓冲区大小。

    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)