Search code examples
.netmemory-managementmemory-leaksoleunmanaged-memory

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


I obtain a pointer to a 0-terminated Unicode string in the pwcsName field of the STATSTG structure, by using the IEnumSTATSTG::Next method.

The memory for it was allocated by OLE, but it is the caller's duty to free it.

I assume the memory was allocated using the unmanaged COM task memory allocator, but I could be wrong here. If the assumption is correct, I'd use Marshal.FreeCoTaskMem(PtrToString) to release that memory.

However, the argument is an IntPtr. In VB I tried to obtain an IntPtr with

PtrToString = CType(pwcsName, IntPtr)

The syntax is accepted, but the string is of wrong type (Input string is not in a correct format.)

Thus my questions:

How would I correctly obtain an IntPtr from the structure record's field?

Or more generally, how can I be the good citizen and prevent that potential memory leak?


This is the relevant code:

    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

Solution

  • If you take a look at the definition of STATSTG you'll read:

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

    So that memory must be freed by CoTaskMemFree() (Marshal.CoTaskMemFree() in .NET).

    Depending on how you are marshaling the struct to .NET, it is possible that .NET will automatically free it. I say "depending" because you are referencing a version of STATSTG that has the pwcsName that is a string: in that case the .NET marshaller will free the OLESTR. If you use a STATSTG struct that uses an IntPtr then you'll have to call CoTaskMemFree.

    To obtain a .NET string you can use Marshal.PtrToStringUni().

    Now... In your specific case, you are in a corner case. While on parameters of methods you can specify if they are [In] or [Out] to let the .NET Marshaller know when it has to "work" to Marshal the data (just before calling the method, or after calling the method, or both ways) (but I don't see them in your Next() definition), on single fields of structs I don't think you can (and then you can't surely modify STATSTG). So what happens here is that after a call to Next(), on the way C->.NET the .NET Marshaller will create a new String from the OLESTR and free the OLESTR. And we are happy. Then on the next call to Next(), before calling the actual API, the .NET Marshaller will see that there is a beautiful String in your struct and marshal it to an OLESTR, thinking it is data you want to pass to Next(). This is clearly useless. And now the problem: we don't know if the implementation of Next() will actually free this OLESTR before creating a new one for the next result (we could do some tests, but we don't want to). The easiest (and surest) solution is to set pwcsName to Nothing (null on C#) BEFORE EACH CALL TO .Next(). In this way the .NET Marshaller will see a beautiful Nothing and will marshal it to null. Less work done by the marshaller (marshalling strings is expensive), more safety for us that don't have to think if the OLESTR will be freed or not.

    Post addendum: I've nugetted the Microsoft.VisualStudio.OLE.Interop 16.7.30328.74 and I've done a Go To Definition from a C# program. The method definition I see is different (and more detailed):

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

    I see that the STATSTG[] rgelt is marked as [Out], so the marshalling will be done only in the C->.NET direction. You don't need to set to Nothing,