假设我有TMemoryStream
我需要传递给我的DLL并从DLL中获取TMemoryStream
(位图流)。
我在想我的DLL会有:
procedure Process(
InBuff: Pointer;
InBuffSize: Integer;
var OutBuff: Pointer;
var OutBuffSize: Integer
); stdcall;
InBuff
很容易(我认为)。我通过了TMemoryStream.Memory
和TMemoryStream.Size
。
问题是如何在DLL中分配OutBuff
,调用者应用程序可以将其转换回TMemoryStream
,然后释放该内存(由调用者应用程序)?
调用者将使用每个DLL调用动态LoadLibrary
/ FreeLibrary
。
我非常想要一个示例代码。希望我不是太粗鲁。
注1:调用者应用程序不知道输出大小,并假设它不能指定MAX buff大小。
注2:我不确定我的DLL签名。如果我做错了,请原谅我。我正在寻找一种效果很好的模式(可能不仅适用于Delphi而且适用于C ++ / C #Calller以及=我的奖金)
答案 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
销毁此信息。如果您愿意,可以使用LocalAlloc
或HeapAlloc
,但这并不重要。
<强> 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;