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

In .NET, how do I free memory for a 0-terminated unmanaged Unicode string, which was allocated by OLE?

我在 pwcsName field of the STATSTG structure, by using the 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, 0, 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, oElement, uiFetched)
    Loop

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

pwcsName
A pointer to a NULL-terminated Unicode string that contains the name. Space for this string is allocated by the method called and freed by the caller (for more information, see CoTaskMemFree).

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

根据您将结构封送到 .NET 的方式,.NET 可能会自动释放它。我说“取决于”是因为您引用的 STATSTG 版本具有 pwcsName,即 string:在这种情况下,.NET 编组器将释放 OLESTR。如果你使用一个使用 IntPtrSTATSTG 结构,那么你必须调用 CoTaskMemFree.

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

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

Post 附录:我对 Microsoft.VisualStudio.OLE.Interop 16.7.30328.74 进行了修改,并且从 C# 程序中完成了 Go To Definition。我看到的方法定义不一样(而且更详细):

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);

我看到 STATSTG[] rgelt 被标记为 [Out],因此编组将仅在 C->.NET 方向进行。您不需要设置为 Nothing,