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

fru*_*ugi 1 c# delphi dll dllexport delphi-xe2

我使用 C# DLL 导出(UnmanagedExports - https://www.nuget.org/packages/UnmanagementExports)使我的托管 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;
    }
Run Code Online (Sandbox Code Playgroud)

Delphi部分(调用部分):

SomeCall: function(data1: PWideChar; data2: PWideChar;): String StdCall;

procedure DoSomeDLLWork(data1: PWideChar; data2: PWideChar);
var 
 dllCallResult: String;
begin
  dllCallResult := SomeCall(data1,data2);
end
Run Code Online (Sandbox Code Playgroud)

本例的问题是只填充了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)
Run Code Online (Sandbox Code Playgroud)

德尔福(调用者):

 SomeCall: function(data1: PWideChar; data2: PWideChar;): boolean StdCall;
Run Code Online (Sandbox Code Playgroud)

现在我必须考虑一个返回值或一个缓冲区来将结果字符串返回到delphi。

编辑2:

我采纳了 David Heffernan 使用 out 参数的建议:

德尔福:

SomeCall: procedure(data1: PWideChar; data2: PWideChar; var result: PWideChar)StdCall;
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

Dav*_*nan 5

问题是string返回值。在 Delphi 中,astring是托管类型。此外,此类类型的待遇有些不寻常。它们实际上是在所有其他参数之后作为额外的隐式var参数传递的。C# 代码通过寄存器传递返回值。

这意味着 C# 函数有 2 个参数,但 Delphi 函数有 3 个参数。这就是解释这种行为的不匹配。

在任何情况下,从 C# 返回字符串都会导致指向正在编组的以 null 结尾的字符数组的指针。它当然不会编组为 Delphi 字符串。

您有一些可用的解决方案:

  1. 保留 C# 并将 Delphi 返回类型更改为PAnsiChar. 或者,PWideChar如果您将 C# 返回值封送为LPWStr. 您需要通过调用来释放指针CoTaskMemFree
  2. 更改 C# 以接受调用者分配的缓冲区并填充。这需要StringBuilderC# 方面。并传递缓冲区的长度。
  3. 将 C# 更改为使用类型为 的输出参数string,编组为UnmanagedType.BStr。这映射到WideString德尔福。

调用者分配缓冲区的问题在于,要求调用者知道要分配多大的缓冲区。

细微差别BStr/WideString是 Delphi 的 ABI 与 Microsoft 的不兼容,请参阅为什么 WideString 不能用作互操作的函数返回值?您可以通过返回字符串作为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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

德尔福

{$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.
Run Code Online (Sandbox Code Playgroud)

输出

富巴