【问题标题】:Issues passing data from DLL to Application将数据从 DLL 传递到应用程序的问题
【发布时间】:2013-02-04 06:19:08
【问题描述】:

我对如何在我的场景中正确使用指针感到有点困惑。我有一个包含一些嵌入式资源的 DLL。我在这个 DLL 中公开了一个函数,它将其中一个资源的二进制数据传递回其调用应用程序。在这种情况下,我嵌入了一个 JPG 图像文件。我的 DLL 确实将文件正确加载到资源流中。但是从那里开始,将其传递回应用程序变得混乱。

这是我的 DLL 代码(加载了 JPG 并命名为 SOMERESOURCE):

library ResDLL;

{$R *.dres}

uses
  System.SysUtils,
  System.Classes,
  Winapi.Windows;

{$R *.res}

function GetResource(const ResName: PChar; Buffer: Pointer;
  var Length: Integer): Bool; stdcall;
var
  S: TResourceStream;
  L: Integer;
  Data: array of Byte;
begin
  Result:= False;
  try
    S:= TResourceStream.Create(HInstance, UpperCase(ResName), RT_RCDATA);
    try
      S.Position:= 0;
      L:= S.Size;
      Length:= L;
      SetLength(Data, L);
      S.Read(Data[0], L);
      Buffer:= @Data[0];
      Result:= True;
    finally
      S.Free;
    end;
  except
    Result:= False;
  end;
end;

exports
  GetResource;

begin
end.

这是我的应用程序代码(只有 TBitBtnTImage):

function GetResource(const ResName: PChar; Buffer: Pointer;
  var Length: Integer): Bool; stdcall; external 'ResDLL.dll';

procedure TForm1.BitBtn1Click(Sender: TObject);
var
  Buffer: array of Byte;
  Size: Integer;
  S: TMemoryStream;
  P: TPicture;
begin
  if GetResource('SOMERESOURCE', @Buffer[0], Size) then begin
    S:= TMemoryStream.Create;
    try
      SetLength(Buffer, Size);
      S.Write(Buffer, Size);
      S.Position:= 0;
      P:= TPicture.Create;
      try
        P.Graphic.LoadFromStream(S);
        Image1.Picture.Assign(P);
      finally
        P.Free;
      end;
    finally
      S.Free;
    end;
  end else begin
    raise Exception.Create('Problem calling DLL');
  end;
end;

看起来好像整个 DLL 调用是成功的,但是接收到的数据是空的(全是 0)。我对Data 之类的东西如何需要被称为Data[0] 以及在什么情况下应该使用以及在什么情况下需要使用@Data 充满好奇。我完全在 DLL 中编写了该代码,而且我不熟悉此类工作,所以我确定我在某个地方搞砸了。我哪里错了?

【问题讨论】:

    标签: delphi dll binary


    【解决方案1】:

    在 DLL 方面,GetResource() 将资源数据读取到本地数组中,而不是将其复制到传递给函数的缓冲区中。将本地数组分配给Buffer 指针不会复制所指向的数据。

    在应用端,BitBtn1Click() 没有为GetResource() 分配任何内存来写入资源数据。即使是这样,您也没有将缓冲区正确写入TMemoryStream。即使你是,你也没有正确地将TMemoryStream 加载到TPicture 中。

    您可以采取几种方法来解决缓冲区问题:

    1) 让GetResource() 分配一个缓冲区并将其返回给应用程序,然后让应用程序在完成后将缓冲区传递回 DLL 以释放它:

    library ResDLL;
    
    {$R *.dres}
    
    uses
      System.SysUtils,
      System.Classes,
      Winapi.Windows;
    
    {$R *.res}
    
    function GetResourceData(const ResName: PChar; var Buffer: Pointer;
      var Length: Integer): Bool; stdcall;
    var
      S: TResourceStream;
      L: Integer;
      Data: Pointer;
    begin
      Result := False;
      try
        S := TResourceStream.Create(HInstance, UpperCase(ResName), RT_RCDATA);
        try
          L := S.Size;
          GetMem(Data, L);
          try
            S.ReadBuffer(Data^, L);
            Buffer := Data;
            Length := L;
          except
            FreeMem(Data);
            raise;
          end;
          Result := True;
        finally
          S.Free;
        end;
      except
      end;
    end;
    
    procedure FreeResourceData(Buffer: Pointer); stdcall;
    begin
      try
        FreeMem(Buffer);
      except
      end;
    end;
    
    exports
      GetResourceData,
      FreeBufferData;
    
    begin
    end.
    

    .

    unit uMain;
    
    interface
    
    uses
      Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
      Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.Buttons, Vcl.ExtCtrls;
    
    type
      TForm1 = class(TForm)
        BitBtn1: TBitBtn;
        Image1: TImage;
        procedure BitBtn1Click(Sender: TObject);
      private
      public
      end;
    
    var
      Form1: TForm1;
    
    implementation
    
    uses
      Vcl.Imaging.jpeg;
    
    {$R *.dfm}
    
    function GetResourceData(const ResName: PChar; var Buffer: Pointer;
      var Length: Integer): Bool; stdcall; external 'ResDLL.dll';
    
    procedure FreeResourceData(Buffer: Pointer); stdcall; external 'ResDLL.dll';
    
    procedure TForm1.BitBtn1Click(Sender: TObject);
    var
      Buffer: Pointer;
      Size: Integer;
      S: TMemoryStream;
      JPG: TJPEGImage;
    begin
      if GetResourceData('SOMERESOURCE', Buffer, Size) then
      begin
        try
          S := TMemoryStream.Create;
          try
            S.WriteBuffer(Buffer^, Size);
            S.Position := 0;
            JPG := TJPEGImage.Create;
            try
              JPG.LoadFromStream(S);
              Image1.Picture.Assign(JPG);
            finally
              JPG.Free;
            end;
          finally
            S.Free;
          end;
        finally
          FreeResourceData(Buffer);
        end;
      end else begin
        raise Exception.Create('Problem calling DLL');
      end;
    end;
    
    end.
    

    2) 让应用向 DLL 查询资源的大小,然后分配一个缓冲区并将其传递给 DLL 进行填充:

    library ResDLL;
    
    {$R *.dres}
    
    uses
      System.SysUtils,
      System.Classes,
      Winapi.Windows;
    
    {$R *.res}
    
    function GetResourceData(const ResName: PChar; Buffer: Pointer;
      var Length: Integer): Bool; stdcall;
    var
      S: TResourceStream;
      L: Integer;
      Data: Pointer;
    begin
      Result := False;
      try
        S := TResourceStream.Create(HInstance, UpperCase(ResName), RT_RCDATA);
        try
          L := S.Size;
          if Buffer <> nil then
          begin
            if Length < L then Exit;
            S.ReadBuffer(Buffer^, L);
          end;
          Length := L;
          Result := True;
        finally
          S.Free;
        end;
      except
      end;
    end;
    
    exports
      GetResourceData;
    
    begin
    end.
    

    .

    unit uMain;
    
    interface
    
    uses
      Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
      Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.Buttons, Vcl.ExtCtrls;
    
    type
      TForm1 = class(TForm)
        BitBtn1: TBitBtn;
        Image1: TImage;
        procedure BitBtn1Click(Sender: TObject);
      private
      public
      end;
    
    var
      Form1: TForm1;
    
    implementation
    
    uses
      Vcl.Imaging.jpeg;
    
    {$R *.dfm}
    
    function GetResourceData(const ResName: PChar; Buffer: Pointer;
      var Length: Integer): Bool; stdcall; external 'ResDLL.dll';
    
    procedure TForm1.BitBtn1Click(Sender: TObject);
    var
      Buffer: array of Byte;
      Size: Integer;
      S: TMemoryStream;
      JPG: TJPEGImage;
    begin
      if GetResourceData('SOMERESOURCE', nil, Size) then
      begin
        SetLength(Buffer, Size);
        if GetResourceData('SOMERESOURCE', @Buffer[0], Size) then
        begin
          S := TMemoryStream.Create;
          try
            S.WriteBuffer(Buffer[0], Size);
            S.Position := 0;
            // alternatively, use TBytesStream, or a custom
            // TCustomMemoryStream derived class, to read
            // from the original Buffer directly so it does
            // not have to be copied in memory...
    
            JPG := TJPEGImage.Create;
            try
              JPG.LoadFromStream(S);
              Image1.Picture.Assign(JPG);
            finally
              JPG.Free;
            end;
          finally
            S.Free;
          end;
          Exit;
        end;
      end;
      raise Exception.Create('Problem calling DLL');
    end;
    
    end.
    

    或者:

    library ResDLL;
    
    {$R *.dres}
    
    uses
      System.SysUtils,
      System.Classes,
      Winapi.Windows;
    
    {$R *.res}
    
    function GetResourceData(const ResName: PChar; Buffer: Pointer;
      var Length: Integer): Bool; stdcall;
    var
      S: TResourceStream;
      L: Integer;
      Data: Pointer;
    begin
      Result := False;
      if (Buffer = nil) or (Length <= 0) then Exit;
      try
        S := TResourceStream.Create(HInstance, UpperCase(ResName), RT_RCDATA);
        try
          L := S.Size;
          if Length < L then Exit;
          S.ReadBuffer(Buffer^, L);
          Length := L;
          Result := True;
        finally
          S.Free;
        end;
      except
      end;
    end;
    
    function GetResourceSize(const ResName: PChar): Integer; stdcall;
    var
      S: TResourceStream;
    begin
      Result := 0;
      try
        S := TResourceStream.Create(HInstance, UpperCase(ResName), RT_RCDATA);
        try
          Result := S.Size;
        finally
          S.Free;
        end;
      except
      end;
    end;
    
    exports
      GetResourceData,
      GetResourceSize;
    
    begin
    end.
    

    .

    unit uMain;
    
    interface
    
    uses
      Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
      Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.Buttons, Vcl.ExtCtrls;
    
    type
      TForm1 = class(TForm)
        BitBtn1: TBitBtn;
        Image1: TImage;
        procedure BitBtn1Click(Sender: TObject);
      private
      public
      end;
    
    var
      Form1: TForm1;
    
    implementation
    
    uses
      Vcl.Imaging.jpeg;
    
    {$R *.dfm}
    
    function GetResourceData(const ResName: PChar; Buffer: Pointer;
      var Length: Integer): Bool; stdcall; external 'ResDLL.dll';
    
    function GetResourceSize(const ResName: PChar): Integer; stdcall; external 'ResDLL.dll';
    
    procedure TForm1.BitBtn1Click(Sender: TObject);
    var
      Buffer: array of Byte;
      Size: Integer;
      S: TMemoryStream;
      JPG: TJPEGImage;
    begin
      Size := GetResourceSize('SOMERESOURCE');
      id Size > 0 then
      begin
        SetLength(Buffer, Size);
        if GetResourceData('SOMERESOURCE', @Buffer[0], Size) then
        begin
          S := TMemoryStream.Create;
          try
            S.WriteBuffer(Buffer[0], Size);
            S.Position := 0;
            JPG := TJPEGImage.Create;
            try
              JPG.LoadFromStream(S);
              Image1.Picture.Assign(JPG);
            finally
              JPG.Free;
            end;
          finally
            S.Free;
          end;
          Exit;
        end;
      end;
      raise Exception.Create('Problem calling DLL');
    end;
    
    end.
    

    【讨论】:

    • 感谢您提供大量示例,我花了大约一天的时间试图弄清楚事情,它左右崩溃了,我把它归结为至少在 DLL 调用中没有崩溃,但是仍然没有运气让它工作。至少我没有问“如何”的无代码问题。这里应该有更多的人主动尝试自己先弄清楚。
    • 就稳定性而言,您个人会推荐哪种方法?我的意思是我知道它们都是稳定的,但是您自己使用哪种方法更可靠?
    • 这真的是个人选择的问题。 1)让应用程序向 DLL 查询资源大小,分配内存并将其传递给 DLL 进行填充,然后在完成后释放内存;或 2) 让 DLL 分配并返回内存,然后让应用程序在完成后将内存传回给 DLL。无论哪种方式都可以,并且都需要两次进入 DLL。就个人而言,我使用 #1,因为这是设置了多少 API 才能工作。
    • Remy,我在您的第一个示例中的代码中发现了 DLL 中的内存泄漏。 An unexpected memory leak has occured. The sizes of unexpected leaked medium and large blocks are 227628
    • 在该示例中不应存在泄漏,除非您将错误的指针传递给 FreeBuffer()。您需要传递 GetResource() 返回的相同指针,以便它被分配它的同一个内存管理器释放。
    【解决方案2】:

    您根本不需要从 DLL 中导出任何函数。您可以直接从主机可执行文件中使用 DLL 的模块句柄。

    您已经将模块句柄传递给资源流构造函数。您正在传递可执行文件的模块句柄。而是传递库的模块句柄。

    var
      hMod: HMODULE;
    ....
    hMod := LoadLibrary('ResDLL');
    try
      S:= TResourceStream.Create(hMod, ...);
      ....
    finally
      FreeLibrary(hMod);
    end;
    

    如果您不想调用 DLL 中的任何函数,如果它是仅资源 DLL,请改用 LoadLibraryExLOAD_LIBRARY_AS_IMAGE_RESOURCE

    hMod := LoadLibraryEx('ResDLL', 0, LOAD_LIBRARY_AS_IMAGE_RESOURCE);
    

    也许您知道 DLL 已加载。例如,它隐式链接到您的可执行文件。在这种情况下,您可以更简单地使用GetModuleHandle 而不是LoadLibraryLoadLibraryEx

    hMod := GetModuleHandle('ResDLL');
    S:= TResourceStream.Create(hMod, ...);
    

    请注意,为了简单说明,我省略了所有错误检查。

    【讨论】:

    • 是的,我知道这一点,但我没有使用它,因为这个 DLL 将做的不仅仅是资源和图像。它还将负责加载未在 DLL 中编译的外部资源以及安全性。我只是想了解数据流的概念。
    • 好的。从问题中我并没有立即清楚这一点。
    • 注意:我已经明确提到了另一个问题:stackoverflow.com/questions/14741696/…
    【解决方案3】:

    将流从 DLL 传递到应用程序的另一种方法是使用接口流。

    implementation
    uses MemoryStream_Interface;
    {$R *.dfm}
    
    Type
    TGetStream = Procedure(var iStream:IDelphiStream);stdcall;
    
    procedure TForm1.Button1Click(Sender: TObject);
    var
     h:THandle;
     p:TGetStream;
     ms :IDelphiStream;
     j:TJpegImage;
    begin
       ms := TInterfacedMemoryStream.Create;
       h := LoadLibrary('ShowStream.dll');
       if h <> 0 then
          try
          @p := GetProcAddress(h,'GetJpegStream');
          p(ms);
          ms.Position := 0;
          j := TJpegImage.create;
          Image1.Picture.Assign(j);
          j.Free;
          Image1.Picture.Graphic.LoadFromStream(TInterfacedMemoryStream(ms));
          finally
          FreeLibrary(h);
          end;
    end;
    

    IDelphiStream 的代码可以在on http://www.delphipraxis.net找到。
    我不会将 MemoryStream_Interface 的内容复制到这篇文章中,因为上述页面的代码中没有版权信息。

    【讨论】:

    • 您的 TGetStream 不应该使用stdcall 约定吗?
    • 你知道我实际上可能会考虑这样做,我假设这不需要共享内存?还是这样?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多