ref string [] утечка памяти при взаимодействии NET / COM - PullRequest
4 голосов
/ 14 апреля 2011

Недавно я обнаружил очень странную (для меня) утечку памяти для COM-объекта IEnumString , используемого из C #.В частности, вызов метода IEnumString.Next с использованием строкового массива, который уже содержал значения, полученные в результате предыдущего вызова, вызвал утечку памяти.

IEnumString выглядело так на стороне C #:

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

Вызовметод RemoteNext (Next), подобный этому, вызвал утечку, которая была проверена путем многократного запуска его в течение длительного времени и непрерывного роста счетчика «Private Bytes».

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

Но при создании нового строкового элемента[] массив для каждого вызова как-то заставлял утечку исчезать.

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

Что не так в первом случае?Я предполагаю, что происходит то, что память COM, выделенная для аргумента rgelt IEnumString.Next, который должен быть освобожден вызывающей стороной, как-то не так.

Но второй случай странно работает.

Редактировать : Для получения дополнительной информации, это то, как выглядит "реализация" метода RemoteNext в ILDASM и .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);

Редактировать 2 : Вы также можете заставить утечку появляться во втором непротекающем случае, просто добавив строковое значение в массив строк (содержащий только нулевые значения) перед вызовом 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);

Так что кажется, что элемент массивадолжен быть пустым для слоя маршалинга, чтобы правильно освобождать неуправляемые строки, скопированные на него.Даже в этом случае массив будет возвращать те же значения, т. Е. Наличие непустого массива не приводит к возвращению неправильных строковых значений, а просто приводит к утечке некоторых.

1 Ответ

1 голос
/ 20 апреля 2011

IEnumString - это всего лишь интерфейс. Что лежит в основе COM-объекта? Это главный подозреваемый.

Посмотрите на неуправляемую декларацию IEnumString:

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

Как видите, второй параметр rgelt - это просто указатель на массив строк - ничего особенного, но когда вы делаете управляемый вызов

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

кажется, что ваша строка в item[0] преобразована в LPOLESTR, который не освобождается должным образом. Поэтому попробуйте это:

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;
    }
}
...