Правильный доступ к SafeArray из VT_UNKNOWN с SafeArrayGetElement - PullRequest
2 голосов
/ 22 октября 2009

У нас есть COM-компонент, реализация и определение интерфейса которого существуют в управляемом коде, но управляются собственным компонентом. Управляемый компонент возвращает SafeArray обратно в собственный код с помощью следующего объявления метода.

interface IExample {
  <return: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VarEnum.VT_UNKNOWN)>
  object[] DoSomeOperation()
}

Сгенерированная собственная подпись правильно передает это обратно как SafeArray.

Во время проверки кода мы задали несколько вопросов о вызовах полученного массива с SafeArrayGetElement. Проблема заключается в том, возвращает ли SafeArrayGetElement экземпляр IUnknown, который является AddRef'd или нет. По сути это сводится к тому, что из следующего является правильным

Пример 1:

CComPtr<IUnknown> spUnk;
hr = SafeArrayGetElement(pArray, &bounds, reinterpret_cast<void**>(&spUnk));

Пример 2:

IUnknown* pUnk;
hr = SafeArrayGetElement(pArray, &bounds, reinterpret_cast<void**>(&pUnk));

Документация на эту тему очень тонкая. Включает только следующую строку.

Если элемент данных является строкой, объектом или вариантом, функция копирует элемент правильным образом.

Определение правильности немного неоднозначно.

Ответы [ 2 ]

2 голосов
/ 23 октября 2009

Первый метод должен быть правильным и должен соответствовать обработке объектов в COM, вероятно, найденное вами определение предполагает, что потребитель знает правильный путь.

Другие упомянутые предметы требуют этого. Копирование VARIANT или SAFEARRAY содержит неявный AddRef (), когда они содержат объекты. VARIANT не требует его, когда присутствует VT_BYREF.

VariantCopy @ MSDN
SafeArrayCopy @ MSDN

Это поведение не присуще SAFEARRAY или VARIANT, поскольку оно является частью правил обработки параметров в COM. Однако ничто не мешает кому-то пытаться обойти правила.

Для входных параметров вызывающий не отвечает перед AddRef (), если он не намеревается сохранить указатель интерфейса для дальнейшего использования. Однако другие случаи использования параметров требуют этого.

Например, интерфейсы, помещенные в VARIANT или другие контейнеры, должны иметь хотя бы один вызов AddRef (), в противном случае это может создать проблемы при использовании VARIANT в качестве выходных параметров из методов COM, поскольку передача данных / ссылок является односторонней. Срок действия исходного объекта может истечь к тому времени, когда вызов прибудет к месту назначения. Точно так же маршалинг интерфейса в Stream также требует AddRef ().

Аналогично, для вызова по ссылке требуется, по крайней мере, один вызов AddRef. Если это не так, то любой подходящий длительный вызов (скажем, через DCOM) может не прийти к месту назначения с гарантией того, что указанный объект все еще жив. Однако здесь часто пропускаются дополнительные вызовы AddRef () / Release (), поскольку объект уже должен быть на уровне 1+ из-за создания в или перед областью вызова.

Если возможно изменить компонент, и ваши вызовы не обрабатываются, то вместо этого может быть желательно использовать GIT. Это позволяет вам вместо этого передавать токен, и будет проще маршалировать интерфейс через COM-квартиры. Время жизни задействованных объектов становится обязанностью вызывающей стороны в течение всего времени вызова, и вы сможете отследить случаи, когда объект не может быть маршалирован.

Создание таблицы глобального интерфейса @ MSDN

Также интересна сноска для BSTR.

Если реализация функции, которая принимает эталонный параметр BSTR, назначает этому параметру новый BSTR, она должна освободить ранее указанный BSTR.

Функции управления строками (COM)

1 голос
/ 22 октября 2009

Это должен быть AddRef: ed, но у меня нет информации из первых рук, что это так (например, я не читал источник).

Я думаю, что документация довольно ясна - копировать указатель интерфейса «правильно» - это AddRef: ing.

Если вы хотите быть действительно уверенным, создайте сверхпростой COM-объект ATL, который реализует IUnknown, поместите несколько из них в SAFEARRAY и установите точку останова в CComObjectBase<>::InternalAddRef (если моя память не изменяет). Затем отладьте вызов на SafeArrayGetElement и посмотрите, достигнута ли ваша точка останова.

...