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

Posted

技术标签:

【中文标题】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)。

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

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

【问题讨论】:

【参考方案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】:

1) Marshal.AllocHGlobal 肯定会工作。根据 Marshal.AllocCoTaskMem 的文档,Marshal.AllocCoTaskMem 也应该可以工作。

2) 使用 Marshal.SizeOf(typeof(WAVEHDR))。 Although you can use the Marshal.SizeOf method, the value returned by this method is not always the same as the value returned by sizeof. Marshal.SizeOf returns the size after the type has been marshaled, whereas sizeof returns the size as it has been allocated by the common language runtime, including any padding.

【讨论】:

【参考方案3】:

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

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

【讨论】:

以上是关于Marshal.AllocHGlobal VS Marshal.AllocCoTaskMem,Marshal.SizeOf VS sizeof()的主要内容,如果未能解决你的问题,请参考以下文章

P Invoke struct结构

halcon c# 内存泄漏 图片存储格式转换

C#byte[]structintptr等的相互转换

知道内存中一个图片的指针IntPtr大小,转换成图片显示

c# 调用c dll void 指针类型转化问题

VS常用技巧