【问题标题】:Marshal.AllocHGlobal VS Marshal.AllocCoTaskMem, Marshal.SizeOf VS sizeof()Marshal.AllocHGlobal VS Marshal.AllocCoTaskMem,Marshal.SizeOf VS sizeof()
【发布时间】:2010-12-25 15:06:17
【问题描述】:

我有以下结构:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct WAVEHDR
{
    internal IntPtr lpData;   // pointer to locked data buffer
    internal uint dwBufferLength; // length of data buffer
    internal uint dwBytesRecorded; // used for input only
    internal IntPtr dwUser;   // for client's use
    internal uint dwFlags;   // assorted flags (see defines)
    internal uint dwLoops;   // loop control counter
    internal IntPtr lpNext;  // reserved for driver
    internal IntPtr reserved;  // reserved for driver
}

我需要分配非托管内存来存储上述结构的实例。指向此结构的指针将传递给 waveOut win32 api 函数(waveOutPrepareHeader、waveOutWrite、waveOutUnprepareHeader)。

  1. 我应该使用Marshal.AllocHGlobal() 还是Marshal.AllocCoTaskMem()?有什么区别?
  2. 我应该将sizeof(WAVEHDR)Marshal.SizeOf(typeof(WAVEHDR))传递给内存分配方法吗?有什么区别?

注意分配的内存必须固定。

【问题讨论】:

    标签: c# .net winapi memory-management unmanaged


    【解决方案1】:

    Windows 程序总是至少有两个堆在其中分配非托管内存。首先是默认进程堆,当 Windows 需要代表程序分配内存时使用它。第二个是 COM 基础结构用来分配的堆。 .NET P/Invoke marshaller 假定此堆已被其函数签名需要取消分配内存的任何非托管代码使用。

    AllocHGlobal 从进程堆分配,AllocCoTaskMem 从 COM 堆分配。

    无论何时编写非托管互操作代码,都应始终避免分配非托管内存的代码与释放它的代码不同的情况。很有可能使用了错误的解除分配器。对于与 C/C++ 程序互操作的任何代码来说尤其如此。这样的程序有自己的分配器,它使用自己的堆,由 CRT 在启动时创建。在其他代码中取消分配此类内存是不可能的,您无法可靠地获取堆句柄。这是 P/Invoke 问题的一个非常常见的来源,尤其是因为 XP 和更早版本中的 HeapFree() 函数会静默忽略未在正确堆中分配的释放内存的请求(泄漏分配的内存),但 Vista 和 Win7 会导致程序有异常。

    在您的情况下无需担心这一点,您使用的 mmsystem API 函数是干净的。它们旨在确保分配的相同代码也解除分配。这是您必须调用waveInPrepareHeader() 的原因之一,它使用最终释放它们的相同代码分配缓冲区。可能使用默认进程堆。

    您只需要分配 WAVEHDR 结构。当你完成它时,你有责任释放它。 mmsystem API 不能为您做这件事,最重要的是因为它们不能可靠地做到这一点。因此,您可以使用任一分配器,您只需要确保调用相应的免费方法。所有 Windows API 都以这种方式工作。我使用 CoTaskMemAlloc() 但确实没有偏好。只是,如果我调用设计不佳的代码,则使用 COM 堆的可能性稍高。

    您不应该在互操作场景中使用 sizeof()。它返回值类型的托管大小。在 P/Invoke 编组器根据 [StructLayout] 和 [MarshalAs] 指令转换结构类型之后,这可能会有所不同。只有 Marshal.SizeOf() 为您提供有保证的正确值。


    更新:VS2012 发生了很大变化。随附的 C 运行时库现在从默认进程堆分配,而不是使用自己的堆。从长远来看,这使得 AllocHGlobal 成为最有可能取得成功的途径。

    【讨论】:

    • 这两个 allocate 函数有性能差异吗?
    • AllocCoTaskMem 性能更高。 AllocHGlobal 调用 LocalAlloc,其中有以下注释:“与其他内存管理函数相比,本地函数的开销更大,提供的功能更少。”见msdn.microsoft.com/en-us/library/windows/desktop/…
    • 这不太可能准确了,从 Win8 开始。 LocalAlloc() 和 CoTaskMemAlloc() 现在都从进程堆中分配。 CRT 更改的可能原因。我从未见过这种剧烈变化的书面充分理由,但假设它与 WinRT(又名 Metra、又名 Store、又名 UWP)有关。最大的问题是你不能再确定你的程序在 Win7 上是内存干净的,需要测试。
    【解决方案2】:
    【解决方案3】:

    2) 据我所知,sizeof 只能用于在编译时具有预定义大小的类型。

    所以使用Marshal.SizeOf(typeof(WAVEHDR))

    【讨论】:

      猜你喜欢
      • 2013-01-25
      • 2013-02-19
      • 2012-04-13
      • 2023-03-10
      • 2017-05-18
      • 2021-04-15
      • 2014-12-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多