在 .NET 中,如何为由 OLE 分配的以 0 结尾的非托管 Unicode 字符串释放内存?

问题描述

我使用 pwcsName 方法获得了指向 STATSTG 结构的 IEnumSTATSTG::Next 字段中以 0 结尾的 Unicode 字符串的指针。

它的内存是由 OLE 分配的,但调用者有责任释放它。

假设内存是使用非托管 COM 任务内存分配器分配的,但我在这里可能是错误的。如果假设正确,我会使用 Marshal.FreeCoTaskMem(PtrToString) 来释放该内存。

然而,参数是一个 IntPtr。在VB中,我试图用

获得一个IntPtr
PtrToString = CType(pwcsName,IntPtr)

接受语法,但字符串类型错误 (Input string is not in a correct format.)

因此我的问题:

如何从结构记录的字段中正确获取 IntPtr

或者更一般地说,我怎样才能成为好公民并防止潜在的内存泄漏?


这是相关代码

    Dim oElements As IEnumSTATSTG = nothing
    oStorage.EnumElements(0,IntPtr.Zero,oElements)

    Dim oElement(0) As Microsoft.VisualStudio.OLE.Interop.STATSTG
    Dim uiFetched As UInt32 = 0

    oElements.Next(1,oElement,uiFetched)
    do while uiFetched > 0
        'Work with oElement(0).pwcsName

        'Attempt to free the memory occupied by the name.
        Dim pName As IntPtr
        pName = CType(.pwcsName,IntPtr)
        Marshal.FreeCoTaskMem(pName)

        Yield ...
        oElements.Next(1,uiFetched)
    Loop

解决方法

如果你看一下 STATSTG 的定义,你会读到:

pwcsName
指向包含名称的以 NULL 结尾的 Unicode 字符串的指针。此字符串的空间由调用者调用和释放的方法分配(有关详细信息,请参阅 CoTaskMemFree)。

因此内存必须由 CoTaskMemFree()(.NET 中的 Marshal.CoTaskMemFree())释放。

根据您如何将结构编组到 .NET,.NET 可能会自动释放它。我说“依赖”是因为您引用的 STATSTG 版本具有 pwcsNamestring:在这种情况下,.NET 编组器将释放 OLESTR。如果您使用使用 STATSTGIntPtr 结构,则必须调用 CoTaskMemFree

要获得 .NET string,您可以使用 Marshal.PtrToStringUni()

现在...在您的特定情况下,您处于极端情况。在方法的参数上,您可以指定它们是 [In][Out] 以让 .NET Marshaller 知道它何时必须“工作”来编组数据(就在调用方法之前,或调用之后方法,或两种方式)(但我在你的 Next() 定义中没有看到它们),在结构的单个字段上我认为你不能(然后你肯定不能修改 {{1} })。所以这里发生的是,在调用 STATSTG 之后,在 C->.NET 的过程中,.NET Marshaller 将从 Next() 创建一个新的 String 并释放 {{1 }}。我们很高兴。然后在对 OLESTRnext 调用中,在调用实际 API 之前,.NET Marshaller 将看到您的结构中有一个漂亮的 OLESTR 并将其编组为 { {1}},认为这是您要传递给 Next() 的数据。这显然是没有用的。现在的问题是:在为下一个结果创建一个新的之前,我们不知道 String 的实现是否真的会释放这个 OLESTR(我们可以做一些测试,但我们不想到)。最简单(也是最可靠)的解决方案是在每次调用 Next() 之前将 Next() 设置为 OLESTR(在 C# 上为pwcsName)。通过这种方式,.NET Marshaller 将看到一个漂亮的 Nothing 并将其编组到 null。编组器完成的工作更少(编组字符串很昂贵),对我们来说更安全,不必考虑 .Next() 是否会被释放。

帖子附录:我已经开发了 Microsoft.VisualStudio.OLE.Interop 16.7.30328.74 并且我已经从 C# 程序中完成了 Go To Definition。我看到的方法定义不同(而且更详细):

Nothing

我看到 null 被标记为 OLESTR,因此只会在 C->.NET 方向上进行编组。您不需要设置为 int Next([In][ComAliasName("Microsoft.VisualStudio.OLE.Interop.ULONG")] uint celt,[Out][ComAliasName("Microsoft.VisualStudio.OLE.Interop.STATSTG")][MarshalAs(UnmanagedType.LPArray,SizeParamIndex = 0)] STATSTG[] rgelt,[ComAliasName("Microsoft.VisualStudio.OLE.Interop.ULONG")] out uint pceltFetched); ,