Ref string [] memory leak using NET / COM interop

I recently discovered a very strange (for me) memory leak for the IEnumString COM object used with C #. In particular, a call to the IEnumString.Next method using a string array that already contains the values ​​obtained from the previous call caused a memory leak.

IEnumString looks like this on the C # side:

[InterfaceType(1)] [Guid("00000101-0000-0000-C000-000000000046")] public interface IEnumString { void Clone(out IEnumString ppenum); void RemoteNext(int celt, string[] rgelt, out int pceltFetched); void Reset(); void Skip(int celt); } 

A call to the RemoteNext (Next) method caused a leak, which was checked by repeating it many times and seeing that the "Private Bytes" counter was growing endlessly.

 string[] item = new string[100]; // OBS! Will be re-used for each call! for (; ; ) { int fetched; enumString.RemoteNext(item.Length, item, out fetched); if (fetched > 0) { for (int i = 0; i < fetched; ++i) { // do something with item[i] } } else { break; } } 

But creating a new string [] array for each call somehow made the leak disappear.

 for (; ; ) { int fetched; string[] item = new string[100]; // Create a new instance for each call. enumString.RemoteNext(item.Length, item, out fetched); if (fetched > 0) { for (int i = 0; i < fetched; ++i) { // do something with item[i] } } else { break; } } 

What happens in the first case? I assume that what happens is that the COM memory allocated for the rgelt argument for IEnumString.Next, which should be freed by the caller, is somehow wrong.

But the second case works strangely.

Change For more information, this is what the "implementation" of the RemoteNext method looks like in ILDASM and .NET Reflector.

 .method public hidebysig newslot abstract virtual instance void RemoteNext([in] int32 celt, [in][out] string[] marshal( lpwstr[ + 0]) rgelt, [out] int32& pceltFetched) runtime managed internalcall { } // end of method IEnumString::RemoteNext [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType=MethodCodeType.Runtime)] void RemoteNext( [In] int celt, [In, Out, MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPWStr, SizeParamIndex=0)] string[] rgelt, out int pceltFetched); 

Change 2 . You can also leak in the second non-leak case by simply adding a string value to a string array (containing only null values) before calling RemoteNext.

 string[] item = new string[100]; // Create a new instance for each call. item[0] = "some string value"; // THIS WILL CAUSE A LEAK enumString.RemoteNext(item.Length, item, out fetched); 

So it seems that the array of elements should be empty for the marshaling layer in order to properly release the unmanaged strings it copied. However, the array will return the same values, that is, the presence of a non-empty array will not return incorrect string values, it will simply skip some of them.

+4
source share
1 answer

IEnumString is just an interface. What is the main COM object? This is the main suspect.

Look at the unmanaged IEnumString :

 HRESULT Next( [in] ULONG celt, [out] LPOLESTR *rgelt, [out] ULONG *pceltFetched ); 

As you can see, the second rgelt parameter is just a pointer to an array of strings - nothing special, but when you control the call

 string[] item = new string[100]; // Create a new instance for each call. item[0] = "some string value"; // THIS WILL CAUSE A LEAK enumString.RemoteNext(item.Length, item, out fetched); 

It seems your string in item[0] converted to LPOLESTR, which is not freed properly. So try the following:

 string[] item = new string[1]; for (; ; ) { int fetched; item[0] = null; enumString.RemoteNext(1, item, out fetched); if (fetched == 1) { // do something with item[0] } else { break; } } 
+1
source

Source: https://habr.com/ru/post/1348072/


All Articles