我有点疑惑如何在我的场景中正确使用指针。我有一个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.
这是我应用的代码(只有TBitBtn
和TImage
):
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中编写了这段代码,我对这些工作并不熟悉,所以我确定我把它搞砸了。我哪里错了?
答案 0 :(得分:10)
在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)让app查询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.
答案 1 :(得分:8)
您无需从DLL中导出任何功能。您可以直接从主机可执行文件中使用DLL的模块句柄。
您已经将模块句柄传递给资源流构造函数。您正在传递可执行文件的模块句柄。相反,传递库的模块句柄。
var
hMod: HMODULE;
....
hMod := LoadLibrary('ResDLL');
try
S:= TResourceStream.Create(hMod, ...);
....
finally
FreeLibrary(hMod);
end;
如果你不想调用DLL中的任何函数,如果它只是一个资源DLL,那么请改用LoadLibraryEx
和LOAD_LIBRARY_AS_IMAGE_RESOURCE
:
hMod := LoadLibraryEx('ResDLL', 0, LOAD_LIBRARY_AS_IMAGE_RESOURCE);
也许你知道DLL已经加载了。例如,它隐式链接到您的可执行文件。在这种情况下,您可以更简单地使用GetModuleHandle
而不是LoadLibrary
或LoadLibraryEx
。
hMod := GetModuleHandle('ResDLL');
S:= TResourceStream.Create(hMod, ...);
请注意,为了简单说明,我省略了所有错误检查。
答案 2 :(得分:2)
将流从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的内容复制到这篇文章中,因为上述页面中的代码没有版权信息。