如何从我的应用程序传递和检索内存流到DLL?

时间:2015-12-10 21:19:51

标签: delphi dll delphi-7

假设我有TMemoryStream我需要传递给我的DLL并从DLL中获取TMemoryStream(位图流)。

我在想我的DLL会有:

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

InBuff很容易(我认为)。我通过了TMemoryStream.MemoryTMemoryStream.Size

问题是如何在DLL中分配OutBuff,调用者应用程序可以将其转换回TMemoryStream,然后释放该内存(由调用者应用程序)?

调用者将使用每个DLL调用动态LoadLibrary / FreeLibrary

我非常想要一个示例代码。希望我不是太粗鲁。

注1:调用者应用程序不知道输出大小,并假设它不能指定MAX buff大小。

注2:我不确定我的DLL签名。如果我做错了,请原谅我。我正在寻找一种效果很好的模式(可能不仅适用于Delphi而且适用于C ++ / C #Calller以及=我的奖金)

3 个答案:

答案 0 :(得分:7)

稍微不同的方法是将每个内存流包装为IStream,并传递生成的接口引用。所以,从DLL的一面:

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;

就个人而言,我也喜欢使用safecall,因为它是一种简单安全的方法,但我想这是一个品味问题。

修改

上述的一个变体是让调用者提供要读取要写入的流的流:

//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;

EXE方可能看起来像这样:

//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;

答案 1 :(得分:4)

两个明显的选项,假设被调用者要分配内存:

<强> 1。使用共享堆

例如,您可以使用COM堆。在被叫者中写道:

OutBuffSize := ...; // you know what this value is
OutBuff := CoTaskMemAlloc(OutBuffSize);
// populate the buffer

来电者用CoTaskMemFree销毁此信息。如果您愿意,可以使用LocalAllocHeapAlloc,但这并不重要。

<强> 2。使用被调用者的堆并导出解除分配器

这里使用被调用者的本机堆:

OutBuffSize := ...; // you know what this value is
GetMem(OutBuff, OutBuffSize);
// populate the buffer

您还需要导出解除分配器:

procedure DeallocateMemory(Ptr: Pointer); stdcall;
begin
  FreeMem(Ptr);
end;

我拒绝的另一个选择是使用共享内存管理器。我倾向于避免这种情况,因为它限制了调用者成为Delphi程序。

从缓冲区调用WriteBuffer填充流:

Stream.WriteBuffer(Buff^, BuffSize);

其中Buff是指向缓冲区的指针。

答案 2 :(得分:3)

  

InBuff很容易(我认为)。我传递了TMemoryStream.Memory和TMemoryStream.Size。

  

问题是如何在DLL中分配OutBuff,调用者应用程序可以将其转换回TMemoryStream,然后释放该内存(由调用者应用程序)?

鉴于您显示的DLL函数的签名,您根本不会在DLL中分配内存。调用者必须分配它。调用者可以调用Process()一次以获得所需的大小,然后分配它,然后再次调用Process()来填充它。这样,调用者负责分配和释放内存。例如:

procedure Process(InBuff: Pointer; InBuffSize: Integer; OutBuff: Pointer; var OutBuffSize: Integer); stdcall;
begin
  //...
  if (OutBuf <> nil) then
  begin
    // copy no more than OutBuffSize bytes into OutBuf, and then
    // update OutBuffSize with the number of bytes actually copied...
    Move(..., OutBuf^, ...);
    OutBuffSize := ...;
  end else begin
    // update OutBuffSize with the number of bytes needed for OutBuff...
    OutBuffSize := ...;
  end;
  //...
end;

var
  InStream: TMemoryStream;
  OutStream: TMemoryStream;
  BuffSize: Integer;
begin
  InStream := TMemoryStream.Create;
  try
    // fill InStream as needed...

    BuffSize := 0;
    Process(InStream.Memory, InStream.Size, nil, BuffSize);

    OutStream := TMemoryStream.Create;
    try
      OutStream.Size := BuffSize;
      Process(InStream.Memory, InStream.Size, OutStream.Memory, BuffSize);
      // use OutStream as needed...
    finally
      OutStream.Free;
    end;
  finally
    InStream.Free;
  end;
end;

如果您确实希望DLL分配内存,则必须更改DLL函数的签名,以使OutBuff成为var参数。您还必须导出一个附加功能,以便DLL可以释放DLL分配的内存。这种方法的好处是调用者只需要调用Process()一次,DLL就可以决定它想要分配和释放内存的方式。例如:

procedure Process(InBuff: Pointer; InBuffSize: Integer; var OutBuff: Pointer; var OutBuffSize: Integer); stdcall;
begin
  //...
  OutBuffSize := ...;
  GetMem(OutBuf, OutBuffSize);
  Move(..., OutBuf^, OutBuffSize);
  //...
end;

procedure FreeProcessBuff(InBuff: Pointer); stdcall;
begin
  FreeMem(InBuff);
end;

type
  TMemoryBufferStream = class(TCustomMemoryStream)
  public
    constructor Create(APtr: Pointer; ASize: NativeInt);
  end;

procedure TMemoryBufferStream.Create(APtr: Pointer; ASize: NativeInt);
begin
  inherited Create;
  SetPointer(APtr, ASize);
end;

...

var
  InStream: TMemoryStream;
  OutStream: TMemoryBufferStream;
  Buff: Pointer;
  BuffSize: Integer;
begin
  InStream := TMemoryStream.Create;
  try
    // fill InStream as needed...

    Buff := nil;
    BuffSize := 0;
    Process(InStream.Memory, InStream.Size, Buff, BuffSize);
    try
      OutStream := TMemoryBufferStream.Create(Buff, BuffSize);
      try
        // use OutStream as needed...
      finally
        OutStream.Free;
      end;
    finally
      FreeProcessBuff(Buff);
    end;
  finally
    InStream.Free;
  end;
end;