代码之家  ›  专栏  ›  技术社区  ›  Erik Ferney Cubillos Garcia

C#Dll库不会将输出参数值返回给Delphi应用程序

  •  2
  • Erik Ferney Cubillos Garcia  · 技术社区  · 8 年前

    我用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;
    

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

    我这样改变了参数

    out int pArchivo
    

    ref int pArchivo
    

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

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

    我在Stackoverflow的这篇文章中读到了这个问题

    Passing array of struct from c# to Delphi

    但就我而言,它不起作用

    我做错了什么?

    我希望你能帮助。。。非常感谢你

    1 回复  |  直到 8 年前
        1
  •  2
  •   Remy Lebeau    8 年前

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

    但是,你的。NET代码只需要数组的1个参数-指向第一个数组元素的原始指针。这就是为什么你没有正确获得你的输出值。你的 lStream.Size 参数值传递到。NET代码期望第三个参数为,因此 Size 值被误解为内存地址。NET代码写入其输出 pArchivo 值为,因此内存错误。不管怎样,这都无关紧要,因为你正在破坏调用堆栈!你最后推了 4. 参数值,然后 stdcall 上。仅网络侧弹出 3.

    您需要更改您的 Funcion 变量,通过以下方式之一:

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

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

      然后您必须更改的呼叫 Funcion() 要为 lArBytes 数组,以便编译器将正确的大小值传递给 pSize

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

      不用说,这不是直观的,尽管它 应该

    • 使用 PByte (或者只是 Pointer 字节数组 :

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

      函数() 要将指针传递给第一个数组元素,并显式传递数组长度,请执行以下操作:

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

    或者,我建议你干脆把你的 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;