Marshal.AllocHGlobal和Marshal.AllocCoTaskMem的区别,以及Marshal.SizeOf和sizeof()的区别。

33

我有以下结构体:

[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))?它们有什么区别?

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

3个回答

51

一个Windows程序至少有两个堆,用于分配未托管内存。第一个是默认的进程堆,当Windows需要代表程序分配内存时使用它。第二个是由COM基础设施用于分配的堆。.NET P/Invoke驱动程序假设这个堆是任何需要释放内存的未托管代码所使用的。

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

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

在您的情况下,不必担心这个问题,您使用的mmsystem API函数是干净的。它们被设计为确保分配和释放内存的代码相同。这是您需要调用waveInPrepareHeader()的原因之一,它使用与最终解除分配它们的代码相同的代码来分配缓冲区。可能使用默认进程堆。

你只需要分配WAVEHDR结构。当你完成后你要负责释放它。mmsystem APIs不会为你做这件事,主要是因为它们无法可靠地执行此操作。因此,你可以使用任何分配器,但你需要确保调用相应的free方法。所有Windows APIs都是这样工作的。我使用CoTaskMemAlloc(),但实际上并没有偏好。只是如果我调用到设计糟糕的代码,稍微有点更可能使用COM堆。

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


更新:在 VS2012 中发生了重大变化。与其自己的堆不同,现在包含在其中的 C 运行时库从默认进程堆中分配。从长远来看,AllocHGlobal 是最有可能成功的途径。


1
这两个分配函数之间有任何性能差异吗? - DxCK
3
AllocCoTaskMem 更高效。AllocHGlobal 调用了 LocalAlloc,而 LocalAlloc 有以下注释:"本地函数的开销更大,提供的内存管理功能比其他内存管理函数更少"。请参见 https://msdn.microsoft.com/en-us/library/windows/desktop/aa366723(v=vs.85).aspx - IamIC
1
这已经不太准确了,从Win8开始。LocalAlloc()和CoTaskMemAlloc()现在都从进程堆中分配内存。CRT更改的可能原因是WinRT(又称Metra、Store、UWP)。最大的问题是你不能确定你的程序在Win7上是否具有内存清理功能,需要进行测试。 - Hans Passant

4

4

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

因此请使用 Marshal.SizeOf(typeof(WAVEHDR))


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