Marshal.AllocHGlobal VS Marshal.AllocCoTaskMem,Marshal.SizeOf VS sizeof()

时间:2009-12-11 10:52:05

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

我有以下结构:

[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
}

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

  1. 我应该使用Marshal.AllocHGlobal()还是Marshal.AllocCoTaskMem()?有什么区别?
  2. 我应该将sizeof(WAVEHDR)Marshal.SizeOf(typeof(WAVEHDR))传递给内存分配方法吗?有什么区别?
  3. 请注意,必须固定分配的内存。

3 个答案:

答案 0 :(得分:41)

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 marshaller根据[StructLayout]和[MarshalAs]指令转换结构类型之后,这可能不一样。只有Marshal.SizeOf()能为您提供有保证的正确值。


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

答案 1 :(得分:2)

答案 2 :(得分:1)

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

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