【问题标题】:Pre-allocate memory between HostApp and DLL在 HostApp 和 DLL 之间预分配内存
【发布时间】:2010-04-12 09:57:52
【问题描述】:

我有一个提供解码功能的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; 

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

有没有更好的解决方案?


是的,Alexander,这可能是一个很好的解决方案。 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: 1) 来电者无法提前确定要分配的金额。 2) 是的。
  • > 哪种分配内存方法更好?在你的情况下,这无关紧要。使用你熟悉的方法(我更喜欢GetMem/FreeMem)。

标签: delphi memory dll


【解决方案1】:

您可以通过其他方式将“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。
【解决方案2】:

您可以使用大多数 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;

编辑:正如mghie 所建议的那样。由于参数是 PCHAR,因此假定 MyDecode 返回的 iLenNeeded 将是 Windows API 所需的(大部分?)标准的 TCHAR 数。

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 中,则效率不会降低。
  • 根据 MyDecode 的具体实现,使用 pchar 可能也不是最好的主意......但我会编辑它并展示另一种用法。
  • 是的,我知道这是 Windows API 解决方案。如果 dll 函数很简单,这可能是最好的解决方案。但是我的“MyDecode”是一个耗时的函数,所以我不想调用它两次。
  • 正如mghie所提到的,可以缓存操作结果,以便在后续调用时只需将数据复制到应用程序提供的缓冲区。无论您使用 threadvar 还是其他机制,开销仍然很小。
  • @Ken:我删除了该评论,因为长字符串解决方案负责处理尾随空字节,因此它可以在Len 的两种含义下正常工作。缓冲区可能有点太大了。
【解决方案3】:

另一种选择是将函数指针传递到 dll 中以分配内存。 dll 在需要内存时调用此函数,并且由于内存是使用应用程序的内存管理器分配的,因此应用程序可以直接释放它。

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

【讨论】:

    【解决方案4】:

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

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

    或者:

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

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

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

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

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-02-15
    • 2014-07-17
    • 2010-11-23
    • 1970-01-01
    • 2016-06-19
    • 2011-04-28
    • 2012-01-24
    相关资源
    最近更新 更多