Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I pass and retrieve memory stream from my Application to/from DLL?

Suppose I have a TMemoryStream I need to pass to my DLL and get back TMemoryStream (Bitmap stream) from the DLL.

I was thinking my DLL would have:

procedure Process(
  InBuff: Pointer; 
  InBuffSize: Integer; 
  var OutBuff: Pointer; 
  var OutBuffSize: Integer
); stdcall;

The InBuff is easy (I think). I pass TMemoryStream.Memory and TMemoryStream.Size.

Question is how do I allocate the OutBuff in the DLL, and the caller application can convert it back to TMemoryStream and later free that memory (by the caller application)?

The caller will use dynamic LoadLibrary/FreeLibrary each DLL call.

I would very much like a sample code. Hope I'm not being too rude.

Note 1: The caller application dose not know the output size, and assume it can not specify a MAX buff size.

Note 2: I am not sure about my DLL signature. Please forgive me if I did it wrong. I am Looking for a pattern that will work well (maybe not only for Delphi but for C++/C# Caller as well = Bonus for me)

like image 726
zig Avatar asked Dec 04 '22 01:12

zig


1 Answers

A slightly different approach is to wrap each memory stream up as a IStream, and pass around the resulting interface references. So, from the DLL's side:

uses
  System.SysUtils, System.Classes, Vcl.AxCtrls;

procedure DoProcess(InStream, OutStream: TStream);
begin
  //...do the actual processing here
end;

//wrapper export
procedure Process(AInStream: IStream; out AOutStream: IStream); safecall;
var
  InStream, OutStream: TStream;
begin
  InStream := TOleStream.Create(AInStream);
  try
    OutStream := TMemoryStream.Create;
    try
      DoProcess(InStream, OutStream);
      AOutStream := TStreamAdapter.Create(OutStream, soOwned);
    except
      OutStream.Free;
      raise;
    end;
  finally
    InStream.Free;
  end;
end;

Personally I like using safecall as well because it's an easy way to be exception-safe, but I guess that's a matter of taste.

Edit

A variant of the above is to have the caller provide both the stream to read and a stream to write to:

//wrapper export
procedure Process(AInStream, AOutStream: IStream); safecall;
var
  InStream, OutStream: TStream;
begin
  InStream := TOleStream.Create(AInStream);
  try
    OutStream := TOleStream.Create(AOutStream);
    try
      DoProcess(InStream, OutStream);
    finally
      OutStream.Free;
    end;
  finally
    InStream.Free;
  end;
end;

The EXE side might then look something like this:

//wrapper import
type
  TDLLProcessProc = procedure(AInStream, AOutStream: IStream); safecall;

procedure Process(AInStream, AOutStream: TStream);
var
  InStream, OutStream: IStream;
  DLLProc: TDLLProcessProc;
  Module: HMODULE;
begin
  InStream := TStreamAdapter.Create(AInStream, soReference);
  OutStream := TStreamAdapter.Create(AOutStream, soReference);
  Module := LoadLibrary(MySuperLib);
  if Module = 0 then RaiseLastOSError;
  try
    DLLProc := GetProcAddress(Module, 'Process');
    if @DLLProc = nil then RaiseLastOSError;
    DLLProc(InStream, OutStream);
  finally
    FreeLibrary(Module);
  end;
end;
like image 149
Chris Rolliston Avatar answered Jan 01 '23 17:01

Chris Rolliston