Современная электронная библиотека ModernLib.Net

Сущность технологии СОМ. Библиотека программиста

ModernLib.Net / Программирование / Бокс Дональд / Сущность технологии СОМ. Библиотека программиста - Чтение (стр. 16)
Автор: Бокс Дональд
Жанр: Программирование

 

 



Снова интерфейс и реализация

Некоторые разработчики всесторонне используют многопоточные программные технологии и способны написать удивительно изощренный программный продукт с использованием примитивов синхронизации потоков, доступных из операционной системы. Другие разработчики больше сконцентрированы на решении вопросов, специфических для их области, и не утруждают себя написанием нудного потоко-безопасного кода. Третьи разработчики имеют особые ограничения по организации потоков, связанные с тем, что многие системы с управлением окнами (включая Windows) имеют очень строгие правила взаимодействия потоков и оконных примитивов. Еще один класс разработчиков может широко использовать старую библиотеку классов, которая неприязненно относится к потокам и не может допустить какого бы то ни было многопоточного обращения. У всех четырех типов разработчиков должна быть возможность использования объектов друг друга без перестраивания их потоковой стратегии, чтобы приспособиться ко всем возможным сценариям. Чтобы облегчить прозрачное использование объекта безотносительно к его осведомленности о потоках, СОМ рассматривает ограничения на параллелизм объектов как еще одну деталь реализации, о которой клиенту не нужно беспокоиться. Чтобы освободить клиента от ограничений параллелизма и реентерабельности (повторной входимости), в СОМ имеется весьма формальная абстракция, которая моделирует связь объектов с процессами и потоками. Эта абстракция носит название апартамент (apartment)[1] . Апартаменты определяют логическое группирование объектов, имеющих общий набор ограничений по параллелизму и реентерабельности. Каждый объект СОМ принадлежит к ровно одному апартаменту: один апартамент, однако, может быть общим для многих объектов. Апартамент, к которому относится объект, безоговорочно является частью идентификационной уникальности объекта.

Апартамент не является ни процессом, ни потоком; но в то же время апартаменты обладают некоторыми свойствами обоих этих понятий. Каждый процесс, использующий СОМ, имеет один апартамент или более; тем не менее, апартамент содержится ровно в одном процессе. Это означает, что каждый процесс, использующий СОМ, имеет как минимум одну группу объектов, которая удовлетворяет требованиям параллелизма и реентерабельности; однако два объекта, находящихся в одном и том же процессе, могут принадлежать к двум различным апартаментам и поэтому иметь разные ограничения по параллелизму и реентерабельности. Этот принцип позволяет библиотекам с совершенно различными понятиями о потоках мирно соучаствовать и одном общем процессе.

Поток одновременно выполняется в одном и только одном апартаменте. Для того чтобы поток мог использовать СОМ, он должен сначала войти в апартамент. Когда поток входит в апартамент, СОМ размещает информацию об апартаменте в локальной записи потока (TLS – thread local storage), и эта информация связана с потоком до тех пор, пока поток не покинет апартамент. СОМ обеспечивает доступ к объектам только для тех потоков, которые выполняются в апартаменте данного объекта. Это означает, что если поток выполняется в том же процессе, что и объект, то потоку может быть запрещено обращаться к объекту, даже если память, которую занимает объект, полностью видима и доступна. В СОМ определен HRESULT (RPC_E_WRONG_THREAD), который возвращают некоторые системные объекты при прямом обращении из других апартаментов. Объекты, определенные пользователем, также могут возвращать этот HRESULT; однако лишь немногие разработчики желают пройти столь долгий путь для обеспечения правильного использования своих объектов.

В версии СОМ под Windows NT 4.0 определяется два типа апартаментов: многопоточные апартаменты (МТА – multithreaded apartments) и однопоточные апартаменты (STA – singlethreaded apartments). В каждом процессе может быть не больше одного МТА; однако процесс может содержать несколько STA. Как следует из этих названий, в МТА может выполняться одновременно несколько потоков, а в STA – только один поток. Точнее, только один поток может когда-либо выполняться в данном STA; что означает не только то, что к объектам, находящимся в STA, никогда нельзя будет обратиться одновременно, но также и то, что только один определенный поток может когда-либо выполнять методы объекта. Эта привязка (affinity) к потоку позволяет разработчикам объекта надежно сохранять промежуточное состояние между вызовами метода в локальной памяти потока TLS (thread local storage), а также сохранять блокировки (locks), требующие поточной привязки (например, критические секции Win32 и семафоры (mutexes)).

Подобная практика приведет к катастрофе, если применять ее в случае объектов из МТА, так как в этом случае неизвестно, какой поток будет выполнять данный вызов метода. Неудобство STA заключается в том, что он позволяет одновременно выполняться только одному вызову метода, независимо от того, сколько объектов принадлежат к данному апартаменту. В случае МТА потоки могут быть распределены динамически на основании текущих запросов, вне зависимости от количества объектов в апартаменте. Для того чтобы создать параллельные серверные процессы с использованием только однопоточных апартаментов, потребуется много апартаментов, и если не соблюдать осторожность, то может возникнуть непомерное количество потоков. Кроме того, уровень параллелизма в основанных на STA обслуживающих процессах не может превышать общее число объектов в процессе. Если процесс сервера содержит только малое число крупномодульных (coarse-grained) объектов, то удастся обойтись малым числом потоков, даже если каждый объект существует в своем отдельном STA .

В будущей реализации СОМ будет, возможно, введен третий тип апартамента – апартамент, арендованный потоками (RTA – rentalthreaded apartment). Подобно МТА, RTA позволяет входить в апартамент более чем одному потоку. Но, в отличие от МТА , когда поток входит в RTA, он вызывает блокировку всего апартамента (apartment-wide lock) (то есть он как бы арендует апартамент), которая запрещает другим потокам одновременно входить в апартамент. Эта блокировка апартамента снимается, когда поток покидает RTA , что позволяет войти следующему потоку. В этом отношении RTA подобен МТА , за исключением того, что все вызовы методов осуществляются последовательно. Это обстоятельство делает RTA значительно удобнее для классов, относительно которых неизвестно, являются ли они потокобезопасными. Хотя все вызовы в STA также осуществляются сериями, объекты на основе RTA отличаются тем, что в них нет привязки потоков; то есть внутри RTA могут выполняться любые потоки, а не только тот исходный поток, который создал апартамент. Эта свобода от привязки к потоку делает объекты на базе RTA более гибкими и эффективными, чем объекты на основе STA, так как любой поток потенциально может сделать вызов объекта, просто войдя в RTA объекта. На момент написания этого текста еще окончательно не определились детали создания апартаментов RTA и входа в них. За подробностями вы можете обратиться к документации по SDK .

Когда поток впервые создается операционной системой как результат вызова CreateProcess или CreateThread, этому новообразованному потоку не сопоставлен ни один апартамент. Перед тем как использовать СОМ, новый поток должен войти в какой-либо апартамент путем вызова одной из приведенных далее API-функций.

В Windows NT 5.0 это будет изменено. За подробностями обращайтесь к документации по SDK.


HRESULT CoinitializeEx(void *pvReserved, DWORD dwFlags);

HRESULT Coinitialize(void *pvReserved);

HRESULT OleInitialize(vo1d *pvReserved);


Для всех трех только что описанных API-функций первый параметр зарезервирован и должен равняться нулю.

CoInitializeEx является API-функцией самого низкого уровня и позволяет вызывающему объекту определять, в какой тип апартамента нужно войти. Для того чтобы войти в МТА всего процесса, вызывающий объект должен использовать флаг COINIT_MULTITHREADED:


HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);


Для входа во вновь соаданный STA вызывающий объект должен выставить флаг COINIT_APARTMENTTHREADED:


HRESULT hr = CoInitializeEx(0, COINIT_APARTMENTTHREADED);


Каждый поток в процессе, который вызывает CoInitializeEx с применением COINIT_MULTITHREADED, выполняется в том же самом апартаменте. Каждый поток, который вызывает CoInitiаlizeEx с применением COINIT_APARTMENTTHREADED, выполняется в отдельном апартаменте, в который не могут входить никакие другие потоки. CoInitialize – это традиционная подпрограмма, которая просто вызывает CoInitializeEx с флагом COINIT_APARTMENTTHREADED. Olelnitialize сначала вызывает CoInitialize, а затем инициализирует несколько подсистем из OLE-приложений, таких как OLE Drag and Drop и OLE Clipboard. Если не предполагается использовать эти службы более высокого уровня, то в общем случае предпочтительнее использовать CoInitialize или CoInitializeEx.

Каждая из этих трех API-функций может быть вызвана больше одного раза на поток. Первый вызов каждого потока возвратит S_ОК. Последующие вызовы просто повторно войдут в тот же апартамент и возвратят S_FALSE. На каждый успешный вызов CoInitialize или CoInitializeEx из того же потока нужно вызвать CoUninitialize. На каждый успешный вызов OleInitialize из того же потока нужно вызвать OleUninitialize. Эти подпрограммы деинициализации (uninitialization) имеют очень простые сигнатуры:


void CoUninitialize(void);

void OleUninitialize(void);


Если все эти подпрограммы перед прекращением потока или процесса не вызваны, это может задержать восстановление ресурсов. Когда поток входит в апартамент, недопустимо изменять типы апартамента с использованием CoInitiаlizeEx. При попытках сделать это HRESULT будет содержать RPC_E_CHANGED_MODE. Однако, если поток полностью покинул апартамент посредством CoUninitialize, он может войти в другой апартамент путем повторного вызова CoInitializeEx.


Объекты, интерфейсы и апартаменты

Клиенты хотят вызывать методы объектов. Объекты просто хотят выставлять свои методы для клиентов. Тот факт, что объект может иметь ограничения на параллелизм (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]. Обязанностью заместителя является передача управления обратно в апартамент объекта для того, чтобы удостовериться, что все вызовы метода выполняются в нужном апартаменте. Эта передача управления от одного апартамента к другому называется удaленным вызовом метода (method remoting ) и является механизмом действия всех межпотоковых, межпроцессных и межмашинных связей в СОМ.

По умолчанию удаленный вызов метода использует имеющийся в СОМ протокол передачи ORPC (Object Remote Procedure Call – вызов объектом удаленной процедуры). СОМ ORPC является упрощенным протоколом MS-RPC (протокол вызова удаленной процедуры Microsoft), производным от DCE (Distributed Computing Environment – распределенная вычислительная среда). MS-RPC является независимым от протокола механизмом связи, который можно расширять с целью поддержки новых транспортных протоколов (посредством динамически загружаемых транспортных DLL) и новых пакетов аутентификации (посредством динамически загружаемых библиотек поставщика поддержки безопасности Security Support Provider DLL ). СОМ использует наиболее эффективный на доступных транспортных протоколов в зависимости от подобия и типов импортирующего и экспортирующего апартаментов. При связи вне хост-машины СОМ предпочитает UDP (User Datagram Protocol – протокол передачи дейтаграмм пользователя), хотя и поддерживает большинство общеупотребительных сетевых протоколов[2]. При локальной связи СОМ использует один из нескольких транспортных протоколов, каждый из которых оптимален для определенного типа апартаментов.

СОМ осуществляет передачу интерфейсных указателей через границы апартаментов с помощью особой технологии, именуемой маршалингом (marshaling), тo есть расположением в определенном порядке, выстраиванием. Маршалинг интерфейсного указателя – это преобразование его в передающийся байтовый поток, содержимое которого единственным образом идентифицирует объект и его собственный апартамент. Этот байтовый поток является маршалированным состоянием (marshaled state) интерфейсного указателя и дает возможность любому апартаменту импортировать интерфейсный указатель и осуществлять вызовы метода на объект. Отметим, что поскольку СОМ имеет дело исключительно с интерфейсными указателями, а не с самими объектами, это состояние маршалинга не представляет собой состояние объекта, а скорее преобразованное в последовательную форму (serialized) состояние не зависящей от апартаментов ссылки на объект. Такие маршалированные объектные ссылки просто содержат информацию об установлении связи, которая совершенно не зависит от состояния объекта.

Обычно указатели интерфейса маршалируются неявно как часть стандартной операции СОМ. Когда запрос на внутрипроцессную активацию сделан для класса с несовместимой моделью поточной обработки, СОМ неявно маршалирует интерфейс из апартамента объекта и демаршалирует заместитель в апартаменте клиента. Если сделан запрос на внепроцессную или внехостовую активацию, то СОМ также маршалирует результирующий указатель из апартамента объекта и демаршалирует заместитель для клиента. Если вызовы метода выполняются на заместители, то любые интерфейсные указатели, проходящие в качестве параметров метода, будут маршалированы с целью сделать объектные ссылки доступными в апартаментах и клиента, и объекта. Иногда необходимо маршалировать интерфейсы явным образом из одного апартамента в другой вне контекста запроса на активацию или вызова метода. Для поддержки этого режима в СОМ предусмотрена API-функция низкого уровня CoMarshalInterface , предназначенная для явного маршалинга интерфейсных указателей.

CoMarshalInterface принимает на входе интерфейсный указатель и записывает преобразованное в последовательную форму представление указателя в предоставленный вызывающим объектом байтовый поток. Этот байтовый поток может затем быть передан в другой апартамент, где API-функция CoUnmarshalInterface использует байтовый поток для возвращения интерфейсного указателя, который семантически эквивалентен исходному объекту, и к которому можно легально обращаться в апартаменте, выполняющем вызов функции CoUnmarshalInterface. При вызове CoMarshalInterface вызывающий объект должен указать, насколько далеко может располагаться импортирующий апартамент. В СОМ определен список рекомендуемых расстояний:


typedef enum tagMSHCTX

{

MSHCTX_INPROC = 4,

// in-process/same host

// внутрипроцессный/тот же хост

MSHCTX_LOCAL = 0,

// out-of-process/same host

// внепроцессный/тот же хост

MSHCTX_NOSHAREDMEM = 1,

// 16/32 bit/same host

// 16/32-битный/тот же хост

MSHCTX_DIFFERENTMACHINE = 2

// off-host

// внехостовый

} MSHCTX;


Допускается указывать большую дистанцию, чем необходимо, но для большей эффективности следует использовать по мере возможности корректное значение MSHCTX. CoMarshalInterface также позволяет вызывающему объекту специфицировать семантику маршалинга с помощью следующих специфических флагов:


typedef enum tagMSHLFLAGS

{

MSHLFLAGS_NORMAL,

// marshal once, unmarshal once

// маршалируем один раз, демаршалируем один раз

MSHLFLAGS_TABLESTRONG,

// marshal опсе, unmarshal many

// маршалируем один раз. демаршалируем много раз

MSHLFLAGS_TABLEWEAK,

// marshal once, unmarshal many

// маршалируем один раз, демаршалируем много раз

MSHLFLAGS_NOPING = 4,

// suppress dist. garbage collection

// подавляем ненужный набор дистанций

} MSHLFLAGS;


Нормальный (normal) маршалинг, иногда его называют еще маршалингом вызовов (call marshaling), означает, что маршалированная объектная ссылка должна быть демаршалирована только один раз, а если нужны дополнительные заместители, то требуются дополнительные вызовы CoMarshalInterface. Табличный (table) маршалинг означает, что маршалированная объектная ссылка может быть демаршалирована нуль и более раз без требования дополнительных вызовов CoMarshalInterface. Подробности табличного маршалинга будут описаны далее в этой главе.

Чтобы разрешить маршалинг интерфейсных указателей на различные носители, функция CoMarshalInterface преобразует интерфейсный указатель в последовательную форму через интерфейс типа IStream , предоставляемый вызывающим объектом. Интерфейс IStream моделирует произвольное устройство ввода-вывода и выставляет методы Read и Write . Функция CoMarshalInterface просто вызывает метод Write на предоставленный вызывающим объектом интерфейс типа IStream , не интересуясь тем, куда эти фактические байты будут записаны. Вызывающие объекты могут получить обертку IStream на необработанную (raw ) память, вызвав API-функцию CreateStreamOnHGlobal :


HRESULT CreateStreamOnHGlobal(

[in] HGLOBAL hglobal,

// pass null to autoalloc

// передаем нуль для автовыдепения памяти

[in] BOOL bFreeMemoryOnRelease,

[out] IStream **ppStm);


С использованием семантики IStream следующий фрагмент кода:


void UseRawMemoryToPrintString(void)

{

void *pv = 0;

// alloc memory

// выделяем память

pv = malloc(13);

if (pv != 0) {

// write a string to the underlying memory

// пишем строку в основную память

memcpy(pv, «Hello, World», 13);

printf((const char*)pv);

// free all resources

// освобождаем все ресурсы free (pv);

}

}


эквивалентен такому фрагменту кода, использующему интерфейс IStream вместо memcpy:


void UseStreamToPrintString(void)

{

IStream *pStm = 0;

// alloc memory and wrap behind an IStream interface

// выделяем память и затем заворачиваем ее в интерфейс

IStream HRESULT hr = CreateStreamOnHGlobal(0, TRUE, &pStm);

if (SUCCEEDED(hr)) {

// write a string to the underlying memory

// записываем строку в память

hr = pStm->Write(«Hello. World», 13, 0);

assert (SUCCEEDED (hr));

// suck out the memory

// извлекаем память

HGLOBAL hglobal = 0;

hr == GetHglobalFromStream(pStm, &hglobal);

assert(SUCCEEDED(hr));

printf((const char*)GlobalLock(hglobal));

// free all resources

// освобождаем все ресурсы

GlobalUnlock(hglobal); pStm->Release();

}

}


API-функция GetHGlobalFromStream позволяет вызывающему объекту получать дескриптор (handle ) памяти, выделенной функцией CreateStreamOnHGlobal. Использование HGLOBAL сложилось исторически и никоим образом не означает использование разделяемой памяти.

После осмысления всех типов параметров API-функции CoMarshalInterface она выглядит достаточно просто:


HRESULT CoMarshalInterface(

[in] IStream *pStm,

// where to write marshaled state

// куда записывать маршалированное состояние

[in] REFIID riid, // type of ptr being marshaled

// тип маршалируемого указателя

[in, iid_is(riid)] IUnknown *pItf,

// pointer being marshaled

// маршалируемый указатепь

[in] DWORD dwDestCtx,

// MSHCTX for destination apt.

// MSHCTX для апартамента адресата


  • Страницы:
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33