2016-12-02 5 views
6

Рассмотрите возможность записи многократно используемой пользовательской функции, которая внутри своего тела создает COM-объекты и вызывает методы для некоторых COM-интерфейсов. Чтобы это нормально работало, необходимо вызывать CoInitializeEx и соответствующие API-интерфейсы CoUninitialize.Инициализация и очистка COM, подходящая для детализации на уровне функции?

Вызов этих API инициализации и очистки COM внутри тело функции скроет деталь реализации COM для вызывающего абонента, а также удалит бремя от вызывающего.

Но звонит CoInitializeEx, а соответствующий корпус CoUninitialize считается хорошей практикой кодирования?

Вызов функций COM init/cleanup на уровне функции-детализации подразумевает слишком много накладных расходов для каждого вызова функции?

Есть ли другие недостатки в этом дизайне?

+0

«* недостатки *" вложенные или рекурсивные вызовы таких функций будут по-прежнему возможны? – alk

+0

@alk - да, эту функцию можно назвать рекурсивной – RbMm

+0

Я чувствую, что это не просто вопрос на C/C++. – alk

ответ

6

Это ужасно Практика и принципиально неправильная. Важно то, что значение для второго аргумента (dwCoInit). Он должен быть COINIT_APARTMENTTHREADED, часто сокращается до STA или COINIT_MULTITHREADED (MTA). Это обещание, которое вы делаете, стиль крест-на-сердце-надежде на смерть. Если вы нарушите обещание, программа умрет. Обычно путем тупиковой ситуации, не получая ожидаемых событий или недопустимо медленного перфорации.

Когда вы выбираете STA, вы обещаете, что поток хорошо себя ведет и может поддерживать COM-компоненты, которые не являются потокобезопасными. Выполнение этого обещания требует, чтобы нить накачивала контур сообщения и никогда не блокировалась. Общее поведение потока, поддерживающего GUI, например. Подавляющее большинство компонентов COM не являются потокобезопасными.

Когда вы выбираете MTA, вы не обещаете никакой поддержки вообще. Компонент должен теперь заботиться о себе, чтобы оставаться в потокобезопасном. Часто выполняется автоматически, когда COM-инфраструктура создает поток сам по себе, чтобы предоставить компоненту безопасный дом. Еще одна деталь, которую вам нужно позаботиться, - это маршалинг указателя интерфейса, требуется вспомогательная функция CoMarshalInterThreadInterfaceInStream() или более удобный интерфейс IGlobalInterfaceTable. Это гарантирует создание прокси-сервера, который будет обслуживать необходимый контекстный контекст потока.

MTA звучит удобно, но не без последствий, простой запрос getter может занять столько же времени, сколько и x10000. Накладные расходы, налагаемые коммутаторами контекста потока, и необходимость копирования любых аргументов и возвращаемого значения в кадры стека. И маршалинг указателя интерфейса может легко потерпеть неудачу, авторы COM-компонентов часто не предоставляют необходимый прокси/заглушку, или они умышленно пропустили его, потому что просто копировать данные просто сложно или дорого.

Ключевым моментом является то, что выбор между STA и MTA никогда не может быть осуществлен библиотекой. Он не знает beans о потоке, он не создал эту нить. И не может знать, работает ли нить на конвейере или блоках сообщений. Это все код, который полностью выходит за пределы библиотеки. В противном случае точная причина, по которой COM-инфраструктура должна это знать, также не может делать предположения о потоке.

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

Fwiw, это то, что вы видите в .NET Framework. CLR всегда вызывает CoInitializeEx(), прежде чем поток сможет выполнить любой управляемый код. Все еще выбор, который должен сделать программист приложения, или, более типично, шаблон проекта, выполненный с атрибутом [STAThread] на Main() или вызовом Thread.SetApartmentState() для рабочего потока.