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

标签 c# delphi dll delphi-xe2 dllexport

我使用 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 字符串。

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

  1. 保留 C#,将 Delphi 返回类型更改为 PAnsiChar .或者 PWideChar如果将 C# 返回值编码为 LPWStr .您需要通过调用 CoTaskMemFree 来释放指针
  2. 更改 C# 以接受它填充的调用者分配的缓冲区。那将需要 StringBuilder在 C# 方面。并传递缓冲区的长度。
  3. 更改 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/

相关文章:

c# - LINQ 查询以对父元素和子元素进行分组

php - Uncaught Error : Call to undefined function mysql_connect() - each time I run my php file it shows me this error

c# - 找不到请求的对象。创建 X509certificate2 时

c# - 将 FixedDocument/XPS 打印为 PDF 而不显示文件保存对话框

c# - 为什么我的 IE 条件注释在未通过 Response.Write 添加时会导致编译错误?

delphi - 将字段和方法添加到从同一基继承的类,而不修改基类

delphi - 使用 Unicode 字符串调用时 StrLComp 与 AnsiStrLComp

delphi - 如何在任意类型上调用 GetEnumerator?

c++ - MFC DLL 调用 AfxWinInit 并使应用程序崩溃

windows - SetDllDirectory 不级联,所以无法加载依赖DLL