I\'m a bit puzzled as to how Pointers should be properly used in my scenario. I have a DLL with some embedded resources in it. I expose a function in this DLL which passes binar
On the DLL side, GetResource()
is reading the resource data into a local array and not copying it into the buffer that is passed to the function. Assigning the local array to the Buffer
pointer does not copy the data being pointed at.
On the app side, BitBtn1Click()
is not allocating any memory for GetResource()
to write the resource data into. Even if it were, you are not writing the buffer into the TMemoryStream
correctly. Even if you were, you are not loading the TMemoryStream
into the TPicture
correctly.
You have a couple of approaches you can take to fix the Buffer issue:
1) have GetResource()
allocate a buffer and return it to the app, then have the app pass the buffer back to the DLL when finished to free it:
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) have the app query the DLL for the size of the resource, then allocate a buffer and pass it to the DLL to fill in:
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.
Or:
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.
You don't need to export any functions at all from your DLL. You can just use the DLL's module handle directly from your host executable.
You are already passing a module handle to the resource stream constructor. You are passing the module handle of the executable. Instead, pass the module handle of the library.
var
hMod: HMODULE;
....
hMod := LoadLibrary('ResDLL');
try
S:= TResourceStream.Create(hMod, ...);
....
finally
FreeLibrary(hMod);
end;
If you don't want to call any functions in the DLL, if it is a resource only DLL, then use LoadLibraryEx
and LOAD_LIBRARY_AS_IMAGE_RESOURCE
instead:
hMod := LoadLibraryEx('ResDLL', 0, LOAD_LIBRARY_AS_IMAGE_RESOURCE);
Perhaps you know that the the DLL is already loaded. For example, it is linked to your executable implicitly. In that case you can more simply use GetModuleHandle
rather than LoadLibrary
or LoadLibraryEx
.
hMod := GetModuleHandle('ResDLL');
S:= TResourceStream.Create(hMod, ...);
Note that I omitted all error checking for the sake of a simple exposition.
Another way passing the stream from a DLL to the application could be using interfaced streams.
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;
The code for IDelphiStream can be found on http://www.delphipraxis.net.
I won't copy the content of MemoryStream_Interface to this post, because there are no copyright informations on the code from the mentioned page.