C# Dll 库不会 return 将参数值输出到 Delphi 应用程序

C# Dll library does not return output parameters value to a Delphi application

我用 C# 编写了一个 Dll,带有一个保存文件的导出函数。

这是 C# 代码

using RGiesecke.DllExport;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.IO;    

namespace ClassLibrary1
{
    public class Class1
    {
        [DllExport("Funcion", CallingConvention = CallingConvention.StdCall)]
        public static void Funcion(IntPtr pDataIn, Int32 pSize, [Out, MarshalAs(UnmanagedType.I4)] int pArchivo)
        {
            byte[] documento = new byte[pSize];
            Marshal.Copy(pDataIn, documento, 0, pSize);
            File.WriteAllBytes("Document2.pdf", documento);            

            pArchivo = 25;
        }
    }    
}

在 Delphi 中,我加载库并调用导出的函数,它工作正常。

这是Delphi代码

procedure TForm1.Button1Click(Sender: TObject);
var
  lStream     : TMemoryStream;
  lArBytes      : array of Byte;
  lInDocSize : Integer;
  lHndle : THandle;

  Funcion : procedure(pDataIn : array of Byte;
                      pInSize : Integer;
                      var pDocumento : Integer
                      ); stdcall;
begin

  try
    lHndle := LoadLibrary('ClassLibrary1.dll');

    if (lHndle <> 0) then
    begin

      Funcion := GetProcAddress(lHndle, 'Funcion');

      if Assigned(Funcion) then
      begin
        try
          lStream := TMemoryStream.Create;

          lStream.LoadFromFile('Document1.PDF');
          lStream.Position := 0;

          SetLength(lArBytes, lStream.Size);

          lStream.Read(lArBytes[0], lStream.Size);

          lInDocSize := 0;
          Funcion(lArBytes, lStream.Size, lInDocSize);

          Label1.Caption := IntToStr(lInDocSize);

        except on E : Exception do
          begin
            RaiseLastOSError;
            ShowMessage(e.Message);
          end;
        end;
      end;
    end;

  finally
  end;

end;

我的输出参数有一个错误,它总是returns cero (0) 值,不管我给参数赋什么值,它总是有 cero 值。

我把参数改成这样

out int pArchivo

ref int pArchivo

但是当函数完成时我得到一个内存异常。

使用 Marshal,函数完成良好,没有内存错误,但输出参数值始终是 cero (0)。

[Out, MarshalAs(UnmanagedType.I4)] int pArchivo

我在 Whosebugpost 中读到了这个问题

Passing array of struct from c# to Delphi

但在我的情况下,它不起作用

我做错了什么?

希望你能帮上忙...非常感谢

在 Delphi 方面,直接声明为 array of ... 的函数参数称为 "open array"。 "open array" 由编译器使用 2 参数传递 - 指向第一个数组元素的指针,以及数组的高索引(不是长度!)。这允许调用代码将静态数组或动态数组传递给同一参数,编译器将相应地传递数组数据。

但是,您的 .NET 代码只需要数组的 1 个参数 - 指向第一个数组元素的原始指针。这就是为什么您没有正确获得输出值的原因。您的 lStream.Size 参数值在 .NET 代码期望第三个参数的位置传递,因此 Size 值被误解为 .NET 代码写入其输出 pArchivo 值的内存地址到,因此内存错误。反正这无关紧要,因为您正在破坏调用堆栈!您最终将 4 参数值推入堆栈,然后 .NET 端的 stdcall 在期间仅弹出 3 参数值函数退出时堆栈清理。

您需要更改 Funcion 变量的声明,方法是:

  • 保留声明为 array of BytepDataIn 参数,但删除显式 pInSize 参数 。让编译器隐式传递它:

    Funcion : procedure(pDataIn : array of Byte;
                        //pInSize : Integer;
                        var pDocumento : Integer
                        ); stdcall;
    

    然后,您必须更改 Funcion() 的调用,以便为 lArBytes 数组再分配 +1 个字节,以便编译器将正确的大小值传递给 pSize 参数:

    SetLength(lArBytes, lStream.Size+1); // <-- +1 here!
    lStream.Read(PByte(lArBytes)^, High(lArBytes)); // <-- no +1 here!
    ...
    Funcion(lArBytes{, High(lArBytes)}, lInDocSize);
    

    不用说,这并不直观,尽管它 应该 工作,因为开放数组的行为是众所周知的,尽管它是 Delphi 编译器.

  • 使用 PByte(或只是 Pointer)而不是 array of Byte:

    Funcion : procedure(pDataIn : PByte; // <-- here
                        pInSize : Integer;
                        var pDocumento : Integer
                        ); stdcall;
    

    然后您必须更改 Funcion() 的调用以传递指向第一个数组元素的指针,并显式传递数组长度:

    SetLength(lArBytes, lStream.Size); // <-- no +1 here!
    lStream.Read(PByte(lArBytes)^, Length(lArBytes)); // <-- or here!
    ...
    Funcion(@lArBytes[0]{or: PByte(lArBytes)}, Length(lArBytes), lInDocSize);
    

    这更直观,更接近 .NET 代码的预期。

或者,我建议您完全删除 lArBytes 变量。你实际上并不需要它。由于 .NET 代码需要一个指向字节数据的原始指针,只需直接传递您的 TMemoryStream 数据:

procedure TForm1.Button1Click(Sender: TObject);
var
  lStream    : TMemoryStream;
  lInDocSize : Integer;
  lHndle : THandle;

  Funcion : procedure(pDataIn : Pointer;
                      pInSize : Integer;
                      var pDocumento : Integer
                      ); stdcall;
begin

  lHndle := LoadLibrary('ClassLibrary1.dll');
  if (lHndle <> 0) then
  try
    Funcion := GetProcAddress(lHndle, 'Funcion');
    if Assigned(Funcion) then
    begin
      lInDocSize := 0;

      lStream := TMemoryStream.Create;
      try
        lStream.LoadFromFile('Document1.PDF');
        Funcion(lStream.Memory, lStream.Size, lInDocSize);
      finally
        lStream.Free;
      end;

      Label1.Caption := IntToStr(lInDocSize);
    end;
  finally
    FreeLibrary(lHndle);
  end;
end;