Issues passing data from DLL to Application

binary, delphi, dll

Solution

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.

Problem

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 binary data of one of those resources back to its calling app. In this case, I've embedded a JPG image file. My DLL does properly load the file into a resource stream. However from there, the passing it back to the app gets messy. Here's my DLL's code (with a 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's my app's code (with just a `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 appears as if the whole DLL call is successful, however the data which was received is empty (full of 0's). I am full of curiosity as to how something like `Data` would need to be called as `Data[0]`, and in what scenarios I should, and also in what scenarios I need to use `@Data`. I wrote that code in the DLL entirely, and I'm not familiar with such work, so I'm sure I botched it up somewhere. Where am I going wrong?

Original source

Related problems