Объекты, интерфейсы и апартаменты
Клиенты хотят вызывать методы объектов. Объекты просто хотят выставлять
свои методы для клиентов. Тот факт, что объект может иметь ограничения
на параллелизм (concurrency constraints), отличные от тех, которые
привносятся клиентским апартаментом, является элементом реализации, о
котором клиент не должен знать. Кроме того, если разработчик объекта сочтет
нужным развернуть реализацию объекта только на малом количестве хост-машин,
которое не содержит той хост-машины, где находится программа клиента,
то это также является деталью реализации, о которой клиент не должен знать.
В любом случае, однако, объект должен находиться в апартаменте, отличном
от апартамента клиента.
С точки зрения программирования, членство в апартаменте является атрибутом
интерфейсного указателя, а не атрибутом объекта. Когда интерфейсный указатель
возвращается после вызова API-функции СОМ или после вызова метода, то
поток, осуществивший вызов API-функции или метода, определяет, к какому
апартаменту принадлежит результирующий интерфейсный указатель. Если вызов
возвращает указатель на текущий объект, то объект сам расположен в апартаменте
вызывающего потока. Часто объект не может находиться в вызывающем апартаменте:
или потому, что объект уже существует и другом процессе или на другой
хост-машине, или потому, что требования параллелизма, присущие этому объекту,
несовместимы с клиентским апартаментом. В этих случаях клиент получает
указатель на заместитель (proxy).
В СОМ заместителем называется объект, семантически идентичный объекту
в другом апартаменте. По смыслу заместитель представляет собой точную
копию объекта в другом апартаменте. Заместитель выставляет тот же набор
интерфейсов, что и представляемый им объект, однако реализация заместителем
каждого из интерфейсных методов просто переадресовывает вызовы на объект,
обеспечивая тем самым то, что методы объекта всегда выполняются в его
апартаменте. Любой интерфейсный указатель, получаемый клиентом от вызова
API-функции или вызова метода, является легальным для всех потоков в апартаменте
вызывающего объекта независимо от того, указывает он на объект или на
заместитель.
Разработчики объектов выбирают типы апартаментов, в которых могут выполняться
их объекты. В главе 6 будет рассмотрено, что внепроцессные серверы явно
задают тип своих апартаментов посредством вызова CoInitializeEx
с соответствующим параметром. Для внутрипроцессных серверов необходим
другой подход, так как CoInitializeEx уже вызывалась клиентом
во время создания объекта. Для того чтобы внутрипроцессные серверы могли
контролировать тип своих апартаментов, в СОМ каждому CLSID разрешается
задавать свою собственную потоковую модель (threading model),
которая объявляется в локальном реестре с использованием переменной под
названием ThreadingModel:
[HKCR\CLSID\ {96556310-D779-11d0-8C4F-0080C73925BA}\InprocServer32]
@="C:\racer.dll"
ThreadingModel="Free"
Каждый CLSID в DLL может иметь индивидуальную ThreadingModel.
Под Windows NT 4.0 СОМ допускает четыре возможных значения ThreadingModel
для CLSID. Значение ThreadingModel="Both"
указывает на то, что класс может выполняться как в МТА, так и
в STA. Значение ThreadingModel="Free" указывает,
что класс может выполняться только в МТА. Значение ThreadingModel="Apartment"
указывает, что класс может выполняться только в STA. Отсутствие
ThreadingModel означает, что класс может выполняться только в
главном STA. Главный STA определяется как первый STA,
который должен быть инициализирован в процессе.
Если апартамент клиента совместим с моделью организации поточной обработки
идентификатора класса CLSID, то все запросы на внутрипроцессную
активацию для этого CLSID будут обрабатывать объект непосредственно
в апартаменте клиента. Это, безусловно, наиболее эффективный сценарий,
так как не требуется никаких промежуточных заместителей1.
Если же апартамент клиента несовместим с моделью организации поточной
обработки, указанной в CLSID, то запросы на внутрипроцесспую
активацию для таких CLSID будут приводить к скрытому созданию
объекта в отдельном апартаменте, а клиенту будет возвращен заместитель.
В случае, когда STA-клиенты активируют классы с ThreadingModel="Free",
объект класса (и последующие его экземпляры) будут выполняться в МТА.
В случае, когда MTA-клиенты активируют классы с ThreadingModel="Apartment",
объект класса (и последующие его экземпляры) будут выполняться в STA,
созданном СОМ. В случае, когда клиенты любого типа активируют классы на
основе главного STA, объект класса (и последующие его экземпляры)
будут выполняться в главном STA процесса. Если же клиент окажется
потоком главного STA, то будет осуществлен прямой доступ к объекту.
В противном случае клиенту будет возвращен заместитель. Если в процессе
нет ни одного STA (то есть если ни один поток не вызвал CoInitiаlizeEx
с флагом COINIT_APARTMENTTHREADED), тогда СОМ создаст новый STA
с тем, чтобы он стал главным STA для процесса.
Разработчики классов, не предусматривающие модель организации поточной
обработки для своих классов, могут большей частью игнорировать проблемы,
связанные с потоками, так как доступ к их библиотекам DLL будет осуществляться
только из одного потока, а именно из главного STA-потока. Те
разработчики, которые предусматривают для своих классов поддержку любой
явной модели организации поточной обработки, косвенно свидетельствуют,
что каждый из множественных апартаментов в процессе (который может быть
многопоточным) может содержать экземпляры класса. Поэтому разработчик
должен защитить любые ресурсы, которые совместно используются более чем
одним экземпляром класса, от параллельного доступа. Это означает, что
все глобальные и статические переменные должны быть защищены с помощью
соответствующего примитива синхронизации потоков. Для внутрипроцессного
сервера СОМ глобальный счетчик блокировок, отслеживающий время жизни сервера,
должен быть защищен с помощью InterlockedIncrement / InterlockedDecrement,
как показано в главе 3. Любое другое специфическое для сервера состояние
также должно быть защищено.
Разработчики, обозначающие свои классы ThreadingModel= "Apartment",
указывают на то, что экземпляры этих классов могут быть доступны только
из одного потока в течение всей жизни объекта. Следонательно, нет необходимости
защищать режим экземпляра, а нужно защищать только режим, общий для нескольких
экземпляров класса, о чем упоминалось ранее. Разработчики, обозначающие
свои классы ThreadingModel="Free" или ThreadingModel="Both"
устанавливают для экземпляров своего класса режим работы в МТА,
что означает, что единовременно возможен доступ только к одному экземпляру
класса. Поэтому разработчики должны защищать все ресурсы, используемые
одним экземпляром, от параллельного доступа. Это относится не только к
общим статическим переменным, но также к элементам данных экземпляра.
Для объектов, расположенных в динамически распределяемой области памяти,
это означает, что элемент данных, отвечающий за счетчик ссылок, должен
быть защищен с помощью InterlockedIncrement/InterlockedDecrement,
как было показано в главе 2. Любое другое состояние экземпляра класса
также должно быть защищено.
На первый взгляд, совершенно не понятно, зачем существует ThreadingModel="Free",
поскольку требования для работы в МТА выглядят как расширенный
набор требований соответствия STA. Если разработчик объекта планирует
рабочие потоки, которым необходим доступ к объекту, то очень полезно предотвратить
создание объекта в каком-либо STA. Дело в том, что рабочие потоки
не могут входить в STA, где живет объект, и поэтому вынуждены
работать в другом апартаменте. Если класс обозначен ThreadingModel="Both"
и запрос на активацию исходит от STA-потока, то объект будет
существовать в STA. Это означает, что рабочие потоки (которые
будут работать в МТА) должны обращаться к объекту через межапартаментные
вызовы методов, значительно менее эффективные, нежели внутриапартаментные
вызовы. Тем не менее, если класс помечен как ThreadingModel="Free",
то любые запросы на активацию со стороны STA вызовут создание
нового экземпляра в МТА, где любые рабочие потоки смогут иметь
прямой доступ к объекту. Это означает, что при вызове клиентом, размещенным
в STA, методов такого объекта, эффективность будет сниженной,
в то время как рабочие потоки будут обрабатываться с большей эффективностью.
Это является приемлемым компромиссом, если рабочие потоки будут обращаться
к объекту чаще, чем действующий клиент из STA. Было бы весьма
соблазнительно смягчить правила СОМ и записать, что не будет ошибкой прямо
обращаться к некоторым объектам из более чем одного апартамента. Однако
в общем случае это неверно, особенно для объектов, которые используют
другие объекты для своей работы.
1 Стоимость выполнения
вызова метода из другого апартамента из-за непроизводительных издержек
по переключению потоков может быть в тысячи раз выше, чем в случае, когда
метод вызывается внутри апартамента.
Межапартаментный доступ
Вспомогательные средства для внутрипроцессного маршалинга
Архитектура стандартного маршалинга
Реализация интерфейсных маршалеров
Стандартный маршалинг, потоки и протоколы
Управление жизненным циклом и маршалинг
Специальный маршалинг
Маршалер свободной поточной обработки (FreeThreaded Marshaler)
Где мы находимся?
|