在HostApp和DLL之间预分配内存

6
我有一个DLL,它提供了一个解码函数,如下所示:
function MyDecode (Source: PChar; SourceLen: Integer; var Dest: PChar; DestLen: Integer): Boolean; stdcall; 

HostApp调用"MyDecode",并传入Source、SourceLen和Dest参数,DLL返回解码后的Dest和DestLen。问题在于:HostApp无法知道解码后的Dest长度,因此不知道如何预先分配Dest的内存。

我知道可以将"MyDecode"拆分为两个函数:

function GetDecodeLen (Source: PChar; SourceLen: Integer): Integer; stdcall;  // Return the Dest's length
function MyDecodeLen (Source: PChar; SourceLen: Integer; var Dest: PChar): Boolean; stdcall; 

但是,我的解码过程非常复杂,如果拆分成两个函数会影响效率。

有更好的解决方案吗?


是的,亚历山大,这可能是一个不错的解决方案。 HostApp 代码:

//... 
MyDecode(....) 
try 
  // Use or copy Dest data 
finally 
  FreeDecodeResult(...) 
end;

DLL代码:

function MyDecode(...): Boolean;
begin
  // time-consuming calculate

  // Allocate memory
  GetMem(Dest, Size);   
  // or New()?
  // or HeapAlloc()?
end;

procedure FreeDecodeResult(Dest: PChar);
begin
  FreeMem(Dest);
  // or Dispose(Dest); ?
  // or HeapFree(Dest); ?
end;

也许我应该将Dest的类型更改为指针。

哪种分配内存方法更好?GetMem/New还是HeapAlloc?


你试图解决哪个问题:1)如何提前确定要分配的数量;2)如何在调用者和被调用者之间协调动态内存管理? - Marcelo Cantos
给Marcelo:1)呼叫者无法提前确定要分配的金额。2)是的。 - Leo
哪种分配内存的方法更好?在你的情况下,这并不重要。使用你熟悉的方法(我更喜欢GetMem/FreeMem)。 - Alex
4个回答

8
你可以通过其他方式将“MyDecode”分成两个程序:

你可以通过其他方式将“ MyDecode”分成两个程序:

function  MyDecode(Source: PChar; SourceLen: Integer; out Dest: PChar; out DestSize: Integer): Boolean; stdcall;
procedure FreeDecodeResult(Dest: PChar); stdcall;

也就是说,在MyDecode中分配内存,而不是要求调用者进行分配。

请注意,如果您为调用方和被调用方使用了一些常见的分配器,那么您可能不需要实现FreeDecodeResult。例如,如果您通过LocalAlloc而不是GetMem来分配内存,则调用方应该调用LocalFree而不是FreeDecodeResult。 - Alex

5
您可以使用大多数Windows API使用的相同技术,即如果您的缓冲区不够大,则函数返回所需的缓冲区大小。这样,您可以从调用函数中分配正确大小的缓冲区。
function MyDecode (Source: PChar; SourceLen: Integer; Dest: PChar; var Len: Integer): Boolean; stdcall;

procedure SomeProc;
var iSourceLen, iLenNeeded : Integer;
    pSource, pDest : Pointer;
begin
  MyDecode(pSource, iSourceLen, nil, iLenNeeded);
  GetMem(pDest,iLenNeeded);
  try
    MyDecode(pSource, iSourceLen,pDest, iLenNeeded);
  finally
    FreeMem(pDest);
  end;
end;
< p > 编辑:如mghie所建议的。由于参数是PCHAR,因此MyDecode返回的iLenNeeded将被认为是需要的TCHAR数,这在Windows API中是标准的(大多数情况下?)。< /p >
function SomeProc(sEncode : String) : string;
var iLenNeeded : Integer;
begin
  MyDecode(PChar(sEncode), Length(sEncode), nil, iLenNeeded);
  SetLength(Result, iLenNeeded);  //if iLenNeeded include a null-terminating character, you can use (iLenNeeded - 1) instead
  if not MyDecode(PChar(sEncode), Length(sEncode), PChar(Result), iLenNeeded) then
    SetLength(Result, 0);
end;

+1,但这可能会引起混淆,因为“Len”既可以表示缓冲区长度,也可以表示字符串长度。为了安全起见,我不会使用GetMem(),而是在字符串上使用SetLength(),这将处理尾随的空字符。关于效率:如果DLL在threadvar中缓存解码数据,则不会降低效率。 - mghie
正如mghie所提到的,操作结果可以被缓存,以便在后续调用中只需要将数据复制到应用程序提供的缓冲区中。无论您使用threadvar还是其他机制,开销都很小。 - Ken Bourassa
@Ken:我删除了那个注释,因为长字符串解决方案会处理尾随的空字节,所以它将正确处理Len的两种含义。缓冲区可能只是有点太大了。 - mghie
该评论很恰当... Length('Hello world') <> Length('Hello world'#0)。长字符串隐式地以 null 结尾,因此如果我需要一个用于 null 结尾字符串的缓冲区,我总是使用 (Len - 1) 来获得适当大小的字符串。如果您真的喜欢,可以在函数末尾调用 Setlength(Result, Length(Result) - 1)... 但在我看来,这是一个无用的重新分配。 - Ken Bourassa
@Ken:如果参数被声明为PChar,那么允许包含'Hello world'#0(空字符是数据的一部分)将会导致API变得非常疯狂。这会在使用类C语言的DLL时打破所有假设。这正是我写下第一条评论关于缓冲区大小与数据长度之间区别的原因。在我看来,你的评论只会让代码读者感到更加复杂。 - mghie
显示剩余5条评论

2

另一种选择是向dll传递一个函数指针来分配内存。当dll需要内存时,它调用此函数,由于使用应用程序的内存管理器分配了内存,因此应用程序可以直接释放它。

不幸的是,这并没有真正解决您的问题,而只是将其移动到dll中,dll必须确定需要多少内存。也许您可以使用存储在链表中的多个缓冲区,因此每次解码函数用尽内存时,它只需分配另一个缓冲区。


1

我不确定这是否适合您,但(在这个特定的例子中)您可以使用WideString:

function MyDecode(Source: PChar; SourceLen: Integer; out Dest: WideString): Boolean; stdcall;

或者:

function MyDecode(Source: PChar; SourceLen: Integer): WideString; stdcall;

通过使用WideString,您可以完全避免内存分配问题。

为什么会这样呢?因为WideString是系统类型BSTR的别名。而BSTR有一个特殊规则:它的内存必须通过特定的系统内存管理器分配。也就是说,当您使用WideString时,Delphi会调用此系统内存管理器而不是自己的内存管理器。由于系统内存管理器可以从每个模块访问(并且对于每个模块都是相同的),这意味着调用者(exe)和被调用者(DLL)将使用相同的内存管理器,从而使它们能够无问题地传递数据。

因此,您可以使用WideString并只需生成结果,而不必担心内存。只需注意,WideString中的字符是Unicode - 即2个字节。如果您使用D2007及以下版本,则需要进行ANSI<->unicode转换,这会带来一些开销。通常情况下,这不是问题,因为典型应用程序会进行大量的WinAPI调用 - 每个WinAPI调用都意味着相同的ANSI<->unicode转换(因为您正在调用A函数)。


是的,由于WideString是BSTR类型,非Delphi应用程序需要使用BSTR,这是系统类型(实际上是COM类型)。请参考此处链接:http://msdn.microsoft.com/en-us/library/ms221069(VS.100).aspxC++中使用BSTR的示例:http://msdn.microsoft.com/en-us/library/xda6xzx7(VS.100).aspx - Alex

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接