Problems transferring data from a DLL to an application

I am a little puzzled by how pointers should be used correctly in my script. I have a DLL with some built-in resources. I expose a function in this DLL that passes the binary data of one of these resources back to the calling application. In this case, I have attached a jpg image file. My DLL loads the file correctly into the resource stream. However, from there, transferring it back to the application becomes messy.

Here is my DLL code (with the JPG loaded and named 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. 

And here is my application code (just TBitBtn and 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; 

It seems that the whole DLL call was successful, however the received data is empty (filled with 0). I am curious as to how something like Data needs to be called as Data[0] , and in which scenarios I should, as well as in which scripts I need to use @Data . I have completely written this code in a DLL, and I am not familiar with such work, so I am sure that I damaged it somewhere. Where am I mistaken?

+4
source share
3 answers

On the DLL side, GetResource() reads the resource data into a local array and does not copy it to the buffer that is passed to the function. Assigning a local array to a Buffer pointer does not copy the data it points to.

On the application side, BitBtn1Click() does not allocate any memory for GetResource() to write resource data. Even so, you are not correctly writing the buffer to TMemoryStream . Even if you were, you would not load the TMemoryStream into TPicture correctly.

You have several approaches that you can take to fix a buffer problem:

1) have GetResource() allocate a buffer and return it to the application, after which the application passes the buffer back to the DLL at the end 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) ask the application to request a DLL for the size of the resource, then allocate a buffer and pass it to the DLL to fill:

 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. 
+10
source

You do not need to export any functions from your DLL. You can simply use the DLL module descriptor directly from the host executable.

You are already passing the module handle to the resource flow constructor. You pass the handle to the executable module. Instead, pass the handle to the library module.

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

If you do not want to call any functions in the DLL, if it is only a resource DLL, use LoadLibraryEx and LOAD_LIBRARY_AS_IMAGE_RESOURCE :

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

You may know that the DLL is already loaded. For example, it is implicitly associated with your executable. In this case, you can simply use GetModuleHandle and not LoadLibrary or LoadLibraryEx .

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

Please note that I skipped all error checking for a simple summary.

+8
source

Another way to transfer a stream from a DLL to an application may be to use conjugate 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; 

Code for IDelphiStream can be found at http://www.delphipraxis.net .
I will not copy the contents of MemoryStream_Interface to this post, because there is no copyright information on the specified page.

+2
source

All Articles