在Delphi中使用C#DLL仅使用第一个函数参数 [英] Using a C# DLL in Delphi only uses the first function parameter

查看:79
本文介绍了在Delphi中使用C#DLL仅使用第一个函数参数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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


  1. 不使用C#并将Delphi返回类型更改为 PAnsiChar 。或者,如果将C#返回值编组为 LPWStr PWideChar 。您需要通过调用 CoTaskMemFree

  2. 释放C#以接受由它填充的调用方分配的缓冲区。这将需要在C#端使用 StringBuilder 。并传递缓冲区的长度。

  3. 更改C#以使用类型为 string 的out参数,将其编组为 UnmanagedType.BStr 。这对应于Delphi中的 WideString

  1. Leave the C# alone and change the Delphi return type to PAnsiChar. Or PWideChar if you marshal the C# return value as LPWStr. You'll need to free the pointer by calling CoTaskMemFree
  2. 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.
  3. Change the C# to use an out parameter of type string, marshalled as UnmanagedType.BStr. That maps to WideString 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆