Выделение BSTR из C ++ в C # с COM-взаимодействием - PullRequest
6 голосов
/ 19 августа 2009

У меня есть внепроцессный COM-сервер, написанный на C ++, который вызывается некоторым клиентским кодом C #. Метод на одном из интерфейсов сервера возвращает клиенту большой BSTR, и я подозреваю, что это вызывает утечку памяти. Код работает, но я ищу помощи по распределению BSTR.

Если немного упростить, IDL для метода сервера -

HRESULT ProcessRequest([in] BSTR request, [out] BSTR* pResponse);

и реализация выглядит так:

HRESULT MyClass::ProcessRequest(BSTR request, BSTR* pResponse)
{
    USES_CONVERSION;
    char* pszRequest = OLE2A(request);
    char* pszResponse = BuildResponse(pszRequest);
    delete pszRequest;
    *pResponse = A2BSTR(pszResponse);
    delete pszResponse;
    return S_OK;
}

A2BSTR внутренне выделяет BSTR с помощью SysAllocStringLen ().

В клиенте C # я просто делаю следующее:

string request = "something";
string response = "";
myserver.ProcessRequest(request, out response);
DoSomething(response);

Это работает, когда строки запроса отправляются на COM-сервер, а правильные строки ответа возвращаются клиенту C #. Но при каждом обращении к серверу происходит утечка памяти в процессе server . Поддержка обнаружения утечки crt не показывает значительных утечек в куче crt, поэтому я подозреваю, что утечка была назначена IMalloc.

Я что-то здесь не так делаю? Я обнаружил смутные комментарии о том, что «все параметры должны быть выделены с помощью CoTaskMemAlloc, в противном случае маршаллер взаимодействия не освободит их», но никаких подробностей.

Andy

Ответы [ 3 ]

3 голосов
/ 19 августа 2009

Я не вижу очевидной проблемы с вашим кодом. Рекомендуется изменить метод ProcessRequest, чтобы исключить взаимодействие COM как источник утечки:

HRESULT MyClass::ProcessRequest(BSTR request, BSTR* pResponse)
{
    *psResponse = ::SysAllocStringLen(L"[suitably long string here]");
    return S_OK;
}

Я подозреваю, что утечка не произойдет, и в этом случае вы сузили утечку своего кода.

Я бы также отметил, что OLE2A выделяет память в стеке, поэтому вы не только не должны удалять pszRequest, но и вообще не должны использовать OLE2A из-за возможности переполнения стека. См. эту статью для более безопасных альтернатив.

Я бы также предложил заменить A2BSTR на :: SysAllocString (CA2W (pszResponse))

2 голосов
/ 19 августа 2009

Анельсон достаточно хорошо это охарактеризовал, но я хотел добавить пару моментов;

  • CoTaskMemAlloc не единственный COM-дружественный распределитель - BSTR распознаются маршаллером по умолчанию и будут освобождены / перераспределены с использованием SysAllocString & friends.

  • Избегая USES_CONVERSION (из-за рисков переполнения стека - см. Ответ Анельсона), ваш полный код должен выглядеть примерно так [1] ​​

(обратите внимание, что A2BSTR безопасен в использовании, так как он вызывает SysAllocString после преобразования и не использует динамическое выделение стека. Кроме того, используйте array-delete (delete []), поскольку BuildResponse, вероятно, выделяет массив символов)

  • Распределитель BSTR имеет кэш, который может заставить его выглядеть так, как если бы произошла утечка памяти. См. http://support.microsoft.com/kb/139071 для некоторых деталей или Google для OANOCACHE. Вы можете попробовать отключить кеш и посмотреть, исчезнет ли «утечка».

[1]

HRESULT MyClass::ProcessRequest(BSTR request, BSTR* pResponse)
{
    char* pszResponse = BuildResponse(CW2A(request));
    *pResponse = A2BSTR(pszResponse);
    delete[] pszResponse;
    return S_OK;
}
0 голосов
/ 19 августа 2009

Полагаю, вам нужно уничтожить request с помощью ::SysFreeString(). Эта память выделяется на стороне сервера.

Также, OLE2A может выделять память из-за преобразования (посмотрите). Вы также не освобождаете это.

...