<<

Рассмотрим идеальный IDL-прототип метода


Next:
HRESULT Next([in] ULONG cElems, [out, size_is(cElems), length_is(*pcFetched)] double *prg, [out] ULONG *pcFetched);
К сожалению, исходное «до-IDL-овское» определение метода
Nextустанавливало, что вызывающие программы могут передавать в качестве третьего параметра нулевой указатель, при условии, что первый параметр показывал, что запрашивается только один элемент.
Это предоставляло вызывающим программам удобную возможность извлекать по одному элементу за раз:
double dblElem;
hr = p->Next(1, &dblElem, 0);
Данное допустимое использование интерфейса противоречит приведенному выше IDL-определению, так как
[out]-параметры самого верхнего уровня не имеют права быть нулевыми (нет места, куда интерфейсный заместитель мог бы сохранять результат). Для разрешения этого противоречия каждое определение метода
Nextдолжно использовать атрибут
[call_as]для замены вызываемой формы (
callable form) метода его отправляемой формой (
remotable form).
Атрибут
[call_as]позволяет разработчику интерфейса выразить один и тот же метод в двух формах. Вызываемая форма метода должна использовать атрибут
[local]для подавления генерирования маршалирующего кода. В этом варианте метода согласовывается, какие клиенты будут вызывать и какие объекты – реализовать. Отправляемая форма метода должна использовать атрибут
[call_as]для связывания генерируемого маршалера с соответствующим методом в интерфейсной заглушке. Этот вариант метода описывает отправляемую форму интерфейса и должен использовать стандартные структуры IDL для описания запроса и ответных сообщений, необходимых для отзыва метода. Применяя технологию
[call_as]к методу
Next, получим такой IDL-код:
interface IEnumDoubIe : IUnknown {
// this method is what the caller and object see
// данный метод, как его видят вызывающая программа и объект
[local] HRESULT Next([in] ULONG cElems,
[out] double *prgElems, [out] ULONG *pcFetched);
// this method is how it goes out on the wire
// данный метод, как он выходит на передачу
[call_as(Next)]
HRESULT RemoteNext([in] ULONG cElems, [out, size_is(cElems), length_is(*pcFetched)] double *prg, [out] ULONG *pcFetched);
HRESULT Skip([in] ULONG cElems);
HRESULT Reset(void); HRESULT Clone([out] IEnumDouble **ppe);
}
Результирующий заголовочный файл C/C++ будет содержать определение интерфейса, включающее в себя метод
Next, но не определение метода
RemoteNext. Что касается клиента и объекта, то у них нет метода
RemoteNext. Он существует только для того, чтобы интерфейсный маршалер мог правильно отправить метод. Хотя у методов
Nextи
RemoteNextсписки параметров идентичны, при использовании данной технологии этого не требуется. На самом деле иногда бывает полезно включить в отправляемую форму метода добавочные параметры, чтобы дать исчерпывающее определение тому, как эта операция будет отправлена.
С добавлением в метод пары атрибутов
[local]/[call_as]исходный код, сгенерированный интерфейсным маршалером, более не сможет успешно компоноваться из-за непреобразованных внешних символов.
Дело в том, что в этом случае разработчик интерфейса должен предусмотреть две дополнительных подпрограммы. Одна из них будет использоваться интерфейсным заместителем для преобразования формы метода с атрибутом
[local]в форму с атрибутом
[call_as]. B случае приведенного выше определения интерфейса компилятор IDL будет ожидать, что разработчик интерфейса обеспечит его следующей функцией:
HRESULT STDMETHODCALLTYPE IEnumDouble_Next_Proxy(IEnumDouble *This, ULONG cElems, double *prg, ULONG *pcFetched);
Вторая необходимая подпрограмма используется интерфейсной заглушкой для преобразования формы метода с атрибутом
[call_as]в форму с атрибутом
[local]. В случае приведенного выше определения интерфейса компилятор IDL будет ожидать от разработчика интерфейса следующей функции:
HRESULT STDMETHODCALLTYPE IEnumDouble_Next_Stub(IEnumDouble *This, ULONG cElems, double *prg, ULONG *pcFetched);
Для удобства прототипы для этих двух подпрограмм будут приведены в сгенерированном заголовочном файле C/C++.
Как показано на рис. 7.10, определяемая пользователем подпрограмма
[local]-to-[call_as]используется для заполнения таблицы
vtblинтерфейсного заместителя и вызывается клиентом. Данная подпрограмма предназначена для преобразования вызова в удаленный вызов процедуры посредством вызова отправляемой версии, которая генерируется компилятором IDL. Для подпрограммы нумератора Next необходимо только убедиться, что в качестве третьего параметра передается ненулевой указатель:
HRESULT STDMETHODCALLTYPE IEnumDouble_Next_Proxy( IEnumDouble *This, ULONG cElems, double *prg, ULONG *pcFetched) {
// enforce semantics on client-side
// осуществляем семантику на стороне клиента
if (pcFetched == 0 && cElems != 1) return E_INVALIDARG;
// provide a location for last [out] param
// обеспечиваем место для последнего [out]-параметра
ULONG cFetched;
if (pcFetched == 0) pcFetched = &cFetched;
// call remote method with non-null pointer as last param
// вызываем удаленный метод с ненулевым указателем
// в качестве последнего параметра
return IEnumDouble_RemoteNext_Proxy(This, cElems, prg, pcFetched);
}
Отметим, что во всех случаях отправляемая версия метода получает в качестве последнего параметра ненулевой указатель.
Определяемая пользователем подпрограмма
[local]-to-[call_as]будет вызываться интерфейсной заглушкой после демаршалинга отправляемой формы метода. Эта подпрограмма предназначена для преобразования отправляемой формы вызова в локальный вызов процедуры на текущий объект. Поскольку реализации объекта иногда проявляют небрежность и не считают нужным показывать, сколько элементов возвращается при возвращении
S_OK, правильность установки этого параметра обеспечивает подпрограмма преобразования со стороны объекта:
HRESULT STDMETHODCALLTYPE IEnumDouble_Next_Stub( IEnumDouble *This, ULONG cElems, double *prg, ULONG *pcFetched) {
// call method on actual object
// вызываем метод на текущий объект
HRESULT hr = This->Next(cElems, prg, pcFetched);
// enforce semantics on object-side
// проводим в жизнь семантику на стороне объекта
if (hr == S_OK)
// S_OK implies all elements sent
// S_OK означает, что все элементы посланы
*pcFetched = cElems;
// [length_is] must be explicit
// атрибут [length_is] должен быть явным
return hr;
}
Интерфейсная заглушка всегда будет вызывать данную подпрограмму с ненулевым последним параметром.
Технология с атрибутом
[call_as]является полезной при организации преобразований из вызываемой формы в отправляемую по схеме «метод-за-методом». В СОМ также предусмотрена возможность специфицировать определяемые пользователем преобразования для отдельных типов данных при помощью атрибутов определения типов
[transmit_as]и
[wire_marshal]. Эти три технологии не следует считать основными при разработке интерфейсов; они существуют в основном для поддержки традиционных идиом и типов данных. Еще одним приемом, которым владеет компилятор IDL, является cpp_quote. Ключевое слово cpp_quote разрешает появление в IDL-файле
любыхоператоров C/C++, даже если этот оператор не является допустимым в IDL. Рассмотрим следующее простейшее применение
cpp_quoteдля внедрения определения встраиваемой функции в сгенерированный IDL заголовочный файл:
// surfboard.idl
cpp_quote(«static void Exit(void) { ExitProcess(1); }»)
Имея данный IDL-код, сгенерированный C/C++ заголовочный файл будет просто содержать следующий фрагмент:
// surfboard.h static void Exit(void) { ExitProcess(1); }
Ключевое слово
cpp_quoteможет быть использовано для осуществления различных трюков в компиляторе IDL. Примером этого может служить тип данных
REFIID. Фактическим определением IDL для этого типа является typedef IID *REFIID;
В то же время тип C++ определен как typedef const IID& REFIID;
Однако ссылки в стиле C++ не допускаются в IDL. Для решения данной проблемы системный IDL-файл использует следующий прием:
// from wtypes.idl (approx.)
// из файла wtypes.idl (приблизительно)
cpp_quote(«#if 0») typedef IID "REFIID;
// this is the pure IDL definition
// это чисто IDL-определение
cpp_quote(«#endif») cpp_quote(«#ifdef _cplusplus») cpp_quote(«#define REFIID const IID&»)
// C++ definition
// определение C++
cpp_quote(«#else») cpp_quote(«#define REFIID const IID * const»)
// С definition
// определение С
cpp_quote(«#endif»)
Результирующий заголовочный файл C++ выглядит так:
// from wtypes.h (approx.)
// из файла wtypes.h (приблизительно)
#if 0 typedef IID *REFIID;
#endif
#ifdef _cplusplus
#define REFIID const IID&
#else
#define REFIID const IID * const
#endif
Этот несколько гротескный прием необходим, поскольку многие базовые интерфейсы СОМ были определены без учета возможного применения IDL.
Асинхронные методы
Вызовы методов в СОМ являются по умолчанию синхронными. Это означает, что клиентский поток заблокирован до тех пор, пока ответное ORPC-сообщение не получено и не демаршалировано. Такая схема в полной мере демонстрирует, как работает обычный вызов метода в одном потоке (
same-thread), и это с полным основанием принято по умолчанию. До появления Windows NT 5.0 не было способа осуществить вызов метода и продолжать обработку одновременно с выполнением метода без явного порождения дополнительных потоков. В версии СОМ Windows NT 5.0 вводится поддержка асинхронного вызова метода. Асинхронность является свойством метода и должна быть выражена в IDL посредством применения атрибута
[async_uuid].
Детали этой технологии во время написания данного текста находились в процессе непрерывного изменения. За подробностями обращайтесь к соответствующей документации.
Где мы находимся?
В данной главе обсуждался ряд тем, относящихся к разработке и использованию интерфейсов СОМ. Хотя эта глава никоим образом не содержит исчерпывающего каталога полезных идиом разработки, в ней была сделана попытка решить несколько существенных вопросов, не обсуждавшихся в предшествующих главах книги. По мере того как мое собственное понимание СОМ развивалось в течение двух лет, потребовавшихся для написания этой книги, я пришел к убеждению, что разработчикам следовало бы уделять меньше внимания специфическим возможностям СОМ (таким, как точки стыковки, моникеры, диспетчерские интерфейсы), а вместо этого сосредоточиться на трех китах СОМ:
интерфейсы, объекты классов, апартаменты.Вооруженный доскональным пониманием этих трех тем, я твердо верю, что нет вершин, которые нельзя было бы покорить с помощью СОМ.
Проиложение А. Эволюция объектов
Сокращенную версию этого очерка предполагается опубликовать в январском, 1998 года, выпускеMicrosoft Systems Journal.
Здесь этот очерк включен в приложение, поскольку в нем СОМ рассматривается в исторической перспективе.
Развитие объектно-ориентированного программирования перешло в стадию коммерческого применения в конце 1980-х годов. Центральной темой объектного ориентирования в середине 1980-х было использование классов, которые позволили разработчикам моделировать состояние и поведение как единый абстрактный модуль. Такая упаковка состояния и поведения помогает провести в жизнь модульный принцип через применение инкапсуляции. В классическом объектном ориентировании объекты принадлежали классам, а клиенты манипулировали объектами посредством основанных на классах ссылок. Такая модель программирования принята в большинстве сред и библиотек C++ и Smalltalk тех времен. В то время программисты, придерживающиеся строгого стиля, могли извлечь максимальную пользу из классового подхода, составляя программы на языках, широко применяющих процедуры. Однако действительно широкое распространение объектно-ориентированного программирования наступило только тогда, когда объектное ориентирование стало явно поддерживаться разработчиками языков программирования и сервисных программ. К числу программных сред, сыгравших важнейшую роль в обеспечении успеха объектного ориентирования, относятся оболочка МасАрр фирмы Apple на базе Object Pascal, первые среды SmallTalk фирм ParePlace и Digitalk, а также Turbo C++ фирмы Borland.
Одним из ключевых преимуществ использования среды разработки, явно поддерживающей объектное ориентирование, была возможность применения полиморфизма для интерпретации групп сходных объектов как совместимых друг с другом по типу. С целью поддержки полиморфизма в объектном ориентировании были введены понятия наследования и динамического связывания, что позволило явно группировать сходные объекты в коллекции (
collections) связанных абстракций. Рассмотрим следующую простейшую иерархию классов C++:
class Dog {
public:
virtual void Bark(void);
};
class Pug : public Dog {
public:
virtual void Bark(void);
};
class Collie : public Dog {
public:
virtual void Bark(void);
};
Поскольку классы
Collieи
Pugоба совместимы по типу с классом
Dog, то клиенты могут написать групповой (
generic) код следующим образом:
void BarkLikeADog(Dog& rdog) {
rdog.Bark();
}
Поскольку метод
Barkявляется виртуальным и динамически связанным, механизмы диспетчеризации методов C++ обеспечивают выполнение нужного кода. Это означает, что функция
BarkLikeADogне полагается на точный тип объекта, на который она ссылается; ей достаточно, чтобы это был тип, совместимый с
Dog. Данный пример может быть легко переделан для любого числа языков, поддерживающих объектно-ориентированное программирование.
Приведенная иерархия классов является типичным примером тех приемов, которые применялись во время первой волны развития объектного ориентирования. Одной из основных характеристик этой первой волны было наследование реализаций. Наследование реализаций является мощным приемом программирования, если применять его строго по правилам. Однако при его неправильном применении результирующая иерархия типов может стать образцом чрезмерной связи между базовым и производным классами. Типичным недостатком такой связи является то, что зачастую неясно, должна реализация метода базовым классом вызываться из версии порожденного класса или нет. Для примера рассмотрим реализацию
Barkкласса
Pug:
void Pug::Bark(void) {
this->BreathIn();
this->ConstrictVocalChords();
this->BreathOut(); }
Что произойдет, если реализация
Barkосновным классом
Dogне вызвана, как в случае приведенного выше фрагмента кода? Возможно, метод базового класса записывает для дальнейшего использования, сколько раз лает (
barks) конкретная собака (
dog)? Если это так, то класс
Pugвторгся в соответствующую часть реализации базового класса
Dog. Для правильного применения наследования реализаций необходимо нетривиальное количество внутреннего знания для обеспечения сохранности базового класса. Это количество детального знания превышает уровень, требующийся для того, чтобы просто быть клиентом базового класса. По этой причине наследование реализации часто рассматривается как повторное использование
белого ящика.
Один из подходов к объектному ориентированию, сокращающий чрезмерную связь систем типов, но сохраняющий преимущества полиморфизма, заключается в том, чтобы наследовать только сигнатуры типов, но не код реализации. Это является фундаментальным принципом разработок
на базе интерфейса,что можно рассматривать как вторую волну объектного ориентирования. Программирование на базе интерфейса является усовершенствованием классического объектного ориентирования, которое считает, что наследование является прежде всего механизмом для выражения отношений между типами, а не между иерархиями реализаций. В основе интерфейсно-ориентированных разработок лежит принцип отделения
интерфейсаот
реализации.В этом направлении интерфейсы и реализации являются двумя различными понятиями. Интерфейсы моделируют абстрактные требования, которые могут предъявляться к объекту. Реализации моделируют конкретные обрабатываемые типы, которые могут поддерживать один или более интерфейсов. Многие из этих преимуществ интерфейсно-ориентированного развития могли быть достигнуты и традиционными средствами первой волны в рамках строгого стиля программирования. Однако широкое принятие этого направления произошло только тогда, когда была получена явная поддержка со стороны разработчиков языков и инструментальных средств программного обеспечения. В число программных сред, сыгравших главную роль в обеспечении успеха интерфейсно-ориентированного развития, входят модель компонентных объектов (Component Object Model – СОМ) фирмы Microsoft, программная среда Orbix Object Request Broker фирмы Iona и Digitalk, а также явная поддержка интерфейсно-ориентированной разработки в рамках языка Java.
Одним из основных преимуществ использования программной среды, поддерживающей интерфейсно– ориентированное развитие, являлась возможность смоделировать, «что» и «как» делает объект, как две различные концепции. Рассмотрим следующую простейшую иерархию типов для Java:
interface IDog {
public void Bark();
};
class Pug implements IDog {
public void Bark( ){…}
};
class Collie Implements IDog {
public void Bark( ){…}
};
Поскольку оба класса –
Collieи
Pug– совместимы с интерфейсом
IDog, то клиенты могут написать групповой код следующим образом:
void BarkLikeADog(IDog dog) {
dog.Bark(); }
С точки зрения клиента, эта иерархия типов практически идентична предыдущему примеру на C++. В то же время, поскольку метод
Barkинтерфейса
IDogне может иметь реализации, между определением интерфейса
IDogи классами
Pugили
Collieне существует связи. Хотя из этого следует, что как
Pug, так и
Collieдолжны полностью определить свое собственное представление о том, что означает «лаять» (
bark), конструкторы
Pugи
Collieне обязаны интересоваться, какие побочные эффекты окажут их производные классы на основной базовый тип
IDog.
Поразительное подобие между первой и второй волной заключается в том, что каждая из них может быть охарактеризована с помощью простого понятия (класс и интерфейс, соответственно). В обоих случаях катализатором успеха послужило не само понятие. Для разжигания интереса со стороны индустрии программирования в целом потребовалась еще одна или несколько ключевых программных сред.
Интересной стороной систем второй волны является то, что реализация рассматривается как черный ящик. Это означает, что все детали реализации считаются непрозрачными (
opaque)для клиентов объекта. Часто, когда разработчики начинают использовать такие основанные на интерфейсах технологии, как СОМ, то уровень свободы, которую дает эта непрозрачность, игнорируется, что побуждает неопытных разработчиков весьма упрощенно рассматривать отношения между интерфейсом, реализацией и объектом. Рассмотрим электронную таблицу Excel, которая выставляет свои функциональные возможности, используя СОМ. Реализация класса электронной таблицы Excel выставляет около 25 различных интерфейсов СОМ, что позволяет ей применять множество основанных на СОМ технологий (Linking, Embedding, Inplace Activation, Automation, Active Document Objects, Hyperlinking и т. д.). Поскольку каждому интерфейсу требуется по четырехбайтному указателю виртуальной функции (
vptr) на объект, объекты электронной таблицы заполняют около 100 байт служебными данными, в добавление к любому конкретному состоянию электронной таблицы, которое может потребоваться для хранения пользовательских данных. Поскольку данный объект электронной таблицы может состоять из весьма большого количества ячеек, эти 100 байт служебных данных погашаются сотнями килобайт, которые может потребовать большая таблица для управления содержимым каждой используемой ячейки.
Фактическая реализация электронной таблицы Excel осложняется тем, что к каждой отдельной ячейке электронной таблицы можно обращаться также через интерфейсы СОМ. С точки зрения СОМ каждый из интерфейсов ячейки представляет собой определенную идентификационную единицу СОМ и не может быть обнаружен с помощью опросов объекта электронной таблицы функцией QueryInterface. Вместо этого интерфейсы ячеек обнаруживаются путем использования одного из альтернативных интерфейсов (например,
IOleItemContainer), которые объект электронной таблицы выставляет для своих клиентов. Тот факт, что теперь каждая ячейка раскрывается для клиентов через интерфейсы СОМ, означает, что разработчик Excel должен позаботиться о недопущении чрезмерного количества служебных данных, относящихся к СОМ. Рассмотрим объект электронной таблицы, состоящей из 1000 ячеек. Предположим для простоты вычислений, что каждой ячейке требуется в среднем по 16 байт памяти для хранения исходного состояния ячейки Excel. Это означает, что таблица из 1000 элементов потребляет примерно 16 000 байт памяти, не связанной с СОМ. Для этой таблицы 100 байт служебных записей указателя виртуальной функции, помещенных интерфейсами табличного уровня, оказывают очень малое влияние на потребление памяти. Однако поскольку каждая отдельная ячейка может самостоятельно выставлять примерно восемь отдельных интерфейсов СОМ, то для каждой ячейки 32 байта могут быть заняты для служебных записей, касающихся управления указателями виртуальных функций ячейки. При использовании простых технологий реализации, которые включены в большинство сред разработки СОМ, 1000-ячеечной электронной таблице понадобится примерно 32 100 байт памяти для указателей виртуальных функций, что примерно вдвое превышает объем памяти, занимаемой исходными данными Excel. Ясно, что такие служебные записи чрезмерны.
Для того чтобы понять, как команда разработчиков Excel решила данную проблему расхода памяти на указатели
vptr, полезно вновь проверить отношения между состоянием и поведением, как оно обычно реализовано в СОМ. На рис. A.1 показан простейший объект СОМ в памяти. Отметим, что блок памяти, занимаемый объектом, состоит из указателей vptr и элементов данных. Можно рассматривать этот рисунок, считая, что элементы данных представляют
состояниеобъекта, а указатели виртуальных функций – его
поведение.В большинстве реализаций объектов эти два аспекта объекта записаны в непрерывном блоке памяти. Однако СОМ не настаивает на этом. СОМ просто имеет дело с указателями
vptr, а управление состоянием предоставляет разработчику. СОМ вполне счастлив, если разработчик решит разместить состояние объекта и
vptrв различных блоках памяти, как показано на рис. А.2. В конце концов, то, как происходит управление состоянием объекта, является всего лишь одной из деталей реализации, скрытой от клиента за стеной интерфейсов объекта.
Так как СОМ не требует, чтобы состояние объекта было размещено рядом с его указателями
vptr, команда разработчиков Excel смогла значительно уменьшить потребление памяти. Рассмотрим отдельную ячейку электронной таблицы. Хотя для записи содержимого ячейки необходимо выделить 16 байт памяти, но 32 байта памяти, необходимых для
vptrячейки, не обязательно размещать в едином блоке памяти вместе с данными ячейки. Кроме того, если к ячейке не осуществляется доступ через ее СОМ-интерфейсы, то эти 32 байта памяти для
vptrвообще не нужны. Это означает, что Excel может просто динамически размещать блоки памяти для
vptr, по принципу «ячейка к ячейке» (
cell-by-cell). Поскольку к большей части ячеек обращения через интерфейсы СОМ не будет никогда, это означает, что фактически в большинстве случаев не будет и затрат на
vptr. Этот принцип создания
«невесомых»объектов (
flyweightobjects), предназначенных для обеспечения поведения по необходимости, является вариантом «отделяемой» (
tearoff) технологии, которая была впервые предложена в великолепной книге Криспина Госвелла «Сборник рецептов программиста СОМ» (Crispin Goswell.
СОМ Programmer's Cookbook) (
http://www.microsoft.com/oledev). Обе эти технологии используют отложенное вычисление (
lazy evaluation) для задержки выделения памяти указателям
vptr.
Невесомые и отделяемые элементы являются технологиями разработки СОМ, однако сама СОМ не дает им полномочий и не поддерживает явно. Эти технологии возникли из необходимости эффективно управлять состоянием. При использовании СОМ для разработки распределенных приложений возникают дополнительные проблемы управления состоянием, в том числе исправление распределенных ошибок, безопасность, управление параллелизмом, уравновешивание загрузки и непротиворечивость данных. К сожалению, СОМ ничего не знает о том, как объект управляет своим состоянием, так что она мало может помочь в разрешении этих проблем. Хотя разработчики могут изобретать свои собственные схемы управления состоянием, имеются явные преимущества в построении общей инфраструктуры для развития объектов со знанием своего состояния. Одной из таких инфраструктур является
Microsoft Transaction Server(Сервер транзакций фирмы Microsoft – MTS).
Модель программирования СОМ расширила традиционную модель объектно-ориентированного программирования, заставив разработчиков вникать во взаимоотношения между интерфейсом и реализацией. Модель программирования с MTS также расширяет модель СОМ, побуждая разработчиков вникать также и во взаимоотношения между состоянием и поведением. Фундаментальный принцип MTS заключается в том, что объект может быть
логическисмоделирован как состояние и поведение, но его
физическаяреализация должна явно различать эти понятия. Явно разрешив MTS управлять состоянием объекта, разработчик приложения может усилить поддержку инфраструктурой управления параллелизмом и блокировкой, локализацией ошибок, непротиворечивостью данных, а также контролем доступа на уровне мелких структурных единиц (
fine-grain). Это означает, что большую часть состояния объекта можно
незаписывать в непрерывный блок с их указателями
vptr(представляющими поведение объекта). Вместо этого в MTS предусмотрены средства для записи состояния объекта либо в длительное, либо во временное хранилище. Это хранилище находится под контролем среды MTS на этапе выполнения, и к нему обеспечен безопасный доступ для методов объекта, причем не нужно заботиться об управлении блокировкой и совместимости данных. Состояние объекта, которое должно оставаться постоянным в случае сбоя машины или нештатного прекращения работы программы, записывается в долговременное хранилище, и MTS гарантирует лишь ничтожные изменения во всей сети. Переходное состояние может быть записано в память, управляемую MTS, причем MTS гарантирует то, что обращения к памяти будут последовательными – во избежание порчи информации.
Как в разработках на базе классов и на базе интерфейсов, модель программирования MTS, конструирующая состояние, требует дополнительного внимания и дисциплины со стороны разработчика. К счастью, как и с разработкой моделей на базе классов и на базе интерфейсов, модель MTS, конструирующая состояние, может быть принята постепенно. Конечно, пошаговое принятие означает, что преимущества MTS будут реализованы также постепенно. Это позволяет разработчикам принимать MTS со скоростью, соответствующей местной культуре программирования.
После объединения команд разработчиков MTS и СОМ в рамках фирмы Microsoft стало ясно, что MTS являет собой следующий шаг в эволюции СОМ. Я горячо призываю всех разработчиков СОМ включиться в эту третью волну развития объектно-ориентированного программирования.
Приложение Б. Избранный код
Исходный код, сопровождающий данную книгу, содержит законченное приложение СОМ (
СОМ Chat) в совокупности с библиотекой кодов утилит, использованных автором. Этот исходный код можно загрузить в электронной форме по адресу
http://www.develop.com/essentialcom. Для удобства приложение
СОМ Chatпредставлено здесь в отпечатанной форме.
СОМ Chat – программа диалогового взаимодействия на базе СОМ
СОМ Chat (чат) является законченной СОМ-программой, которая реализует рассредоточенное приложение диалогового взаимодействия, состоящее из нескольких разделов. Это приложение состоит из трех двоичных компонентов:
comchat.exe– интерактивный сервер,
comchatps.dll– интерфейсный маршалер для всех интерфейсов СОМ Chat,
client.exe– клиентское приложение, основанное на консоли.
Приложение базируется на единственном классе СОМ (
CLSID_ChatSession). Как показано на рис. B.1, объект класса реализует интерфейс
IChatSessionManager, а каждый сеанс связи (
chat session) реализует интерфейс
IChatSession. Клиенты, желающие получать извещения чата, должны подсоединить интерфейс
IChatSessionEventsк объекту сеанса связи.
COMChat.idl
/////////////////////////////////////////////////////
//
// COMChat.idl
//
// Copyright 1997, Don Box/Addison Wesley
//
// This code accompanies the book "The Component
// Object Model" from Addison Wesley. Blah blah blah
//
//
interface IChatSessionEvents;
[
uuid(5223A050-2441-11d1-AF4F-0060976AA886),
object
]
interface IChatSession : IUnknown
{
import «objidl.idl»;
[propget] HRESULT SessionName([out, string] OLECHAR **ppwsz);
HRESULT Say([in, string] const OLECHAR *pwszStatement);
HRESULT GetStatements([out] IEnumString **ppes);
HRESULT Advise([in] IChatSessionEvents *pEventSink,
[out] DWORD *pdwReg);
HRESULT Unadvise([in] DWORD dwReg);
}
[
uuid(5223A051-2441-11d1-AF4F-0060976AA886),
object
]
interface IChatSessionEvents : IUnknown
{
import «objidl.idl»;
HRESULT OnNewUser([in, string] const OLECHAR *pwszUser);
HRESULT OnUserLeft([in, string] const OLECHAR *pwszUser);
HRESULT OnNewStatement([in, string] const OLECHAR *pwszUser,
[in, string] const OLECHAR *pwszStmnt);
}
[
uuid(5223A052-2441-11d1-AF4F-0060976AA886),
object
]
interface IChatSessionManager : IUnknown
{
import «objidl.idl»;
HRESULT GetSessionNames([out] IEnumString **ppes);
HRESULT FindSession([in, string] const OLECHAR *pwszName,
[in] BOOL bDontCreate,
[in] BOOL bAllowAnonymousAccess,
[out] IChatSession **ppcs);
HRESULT DeleteSession([in, string] const OLECHAR *pwszName);
}
cpp_quote(«DEFINE_GUID(CLSID_ChatSession,0x5223a053,0x2441,»)
cpp_quote(«0x11d1,0xaf,0x4f,0x0,0x60,0x97,0x6a,0xa8,0x86);»)
client.cpp
/////////////////////////////////////////////////////
//
// client.cpp
//
// Copyright 1997, Don Box/Addison Wesley
//
// This code accompanies the book "The Component
// Object Model" from Addison Wesley. Blah blah blah
//
//
#define _WIN32_WINNT 0x403
#include
#include
#include
#include
#include «../include/COMChat.h»
#include «../include/COMChat_i.c»
void Error(HRESULT hr, const char *psz)
{
printf(«%s failed and returned 0x%x\n», psz, hr);
}
// utility function to print command line syntax
int Usage(void)
{
const char *psz =
«usage: client.exe \n»
« where:\n»
« action = /sessions|/chat:session|/delete:session\n»
« user = /user:domain\\user /password:pw |»
«/anonymous | \n»
« host = /host:hostname | \n»;
printf(psz);
return -1;
}
// utility function for printing a list of strings
void PrintAllStrings(IEnumString *pes)
{
enum { CHUNKSIZE = 64 };
OLECHAR *rgpwsz[CHUNKSIZE];
ULONG cFetched;
HRESULT hr;
do
{
hr = pes->Next(CHUNKSIZE, rgpwsz, &cFetched);
if (SUCCEEDED(hr))
{
for (ULONG i = 0; i < cFetched; i++)
if (rgpwsz[i])
{
wprintf(L"%s\n", rgpwsz[i]);
CoTaskMemFree(rgpwsz[i]);
}
}
} while (hr == S_OK);
}
// utility function to print initial state of
// a chat session
void PrintToDate(IChatSession *pcs)
{
IEnumString *pes = 0;
HRESULT hr = pcs->GetStatements(&pes);
if (SUCCEEDED(hr))
{
PrintAllStrings(pes);
pes->Release();
}
}
// this class implements the callback interface
// that receives chat notifications. It simply
// prints the event to the console
class EventSink : public IChatSessionEvents
{
public:
STDMETHODIMP QueryInterface(REFIID riid, void**ppv)
{
if (riid == IID_IUnknown)
*ppv = static_cast(this);
else if (riid == IID_IChatSessionEvents)
*ppv = static_cast(this);
else
return (*ppv = 0), E_NOINTERFACE;
reinterpret_cast(*ppv)->AddRef();
return S_OK;
}
STDMETHODIMP_(ULONG) AddRef(void)
{
return 2;
}
STDMETHODIMP_(ULONG) Release(void)
{
return 1;
}
STDMETHODIMP OnNewStatement(const OLECHAR *pwszUser,
const OLECHAR *pwszStmt)
{
wprintf(L"%-14s: %s\n", pwszUser, pwszStmt);
return S_OK;
}
STDMETHODIMP OnNewUser(const OLECHAR *pwszUser)
{
wprintf(L"\n\n>>> Say Hello to %s\n\n", pwszUser);
return S_OK;
}
STDMETHODIMP OnUserLeft(const OLECHAR *pwszUser)
{
wprintf(L"\n\n>>> Say Bye to %s\n\n", pwszUser);
return S_OK;
}
};
// type of operations this client can perform
enum ACTION
{
ACTION_NONE,
ACTION_CHAT,
ACTION_DELETE_SESSION,
ACTION_LIST_SESSION_NAMES,
};
// run chat command
void Chat(const OLECHAR *pwszSession,
IChatSessionManager *pcsm, // manager
COAUTHIDENTITY *pcai, // user
bool bAnonymous) // anonymous
{
// create or get the named session
IChatSession *pcs = 0;
HRESULT hr = pcsm->FindSession(pwszSession, FALSE,
TRUE, &pcs);
if (SUCCEEDED(hr))
{
// adjust security blanket for session interface
if (!bAnonymous)
hr = CoSetProxyBlanket(pcs, RPC_C_AUTHN_WINNT,
RPC_C_AUTHZ_NONE, 0,
RPC_C_AUTHN_LEVEL_PKT,
RPC_C_IMP_LEVEL_IDENTIFY,
pcai, EOAC_NONE);
// catch up on past messages
PrintToDate(pcs);
// hook up event sink to receive new messages
EventSink es;
DWORD dwReg;
hr = pcs->Advise(&es, &dwReg);
if (SUCCEEDED(hr))
{
// run UI loop to get statements from console and send them
OLECHAR wszStmt[4096];
while (_getws(wszStmt))
{
hr = pcs->Say(wszStmt);
if (FAILED(hr))
Error(hr, «Say»);
}
// tear down connection for event sink
pcs->Unadvise(dwReg);
}
else
Error(hr, «Advise»);
// release chat session
pcs->Release();
}
else
Error(hr, «FindSession»);
}
// run delete command
void Delete(const OLECHAR *pwszSession,
IChatSessionManager *pcsm)
{
HRESULT hr = pcsm->DeleteSession(pwszSession);
if (FAILED(hr))
Error(hr, «DeleteSession»);
}
// run list command
void List(IChatSessionManager *pcsm)
{
IEnumString *pes = 0;
HRESULT hr = pcsm->GetSessionNames(&pes);
if (SUCCEEDED(hr))
{
printf(«Active Sessions:\n»);
PrintAllStrings(pes);
pes->Release();
}
}
int main(int argc, char **argv)
{
// declare client control state
bool bAnonymous = false;
static OLECHAR wszSessionName[1024];
static OLECHAR wszDomainName[1024];
static OLECHAR wszUserName[1024];
static OLECHAR wszPassword[1024];
static OLECHAR wszHostName[1024];
COSERVERINFO csi = { 0, wszHostName, 0, 0 };
COSERVERINFO *pcsi = 0;
COAUTHIDENTITY cai = {
wszUserName,
0,
wszDomainName,
0,
wszPassword,
0,
SEC_WINNT_AUTH_IDENTITY_UNICODE
};
static COAUTHIDENTITY *pcai = 0;
static ACTION action = ACTION_NONE;
// parse command line
for (int i = 1; i < argc; i++)
{
if (strcmp(argv[i], «/anonymous») == 0)
bAnonymous = true;
else if (strstr(argv[i], «/delete:») == argv[i])
{
if (action != ACTION_NONE)
return Usage();
action = ACTION_DELETE_SESSION;
mbstowcs(wszSessionName, argv[i] + 8, 1024);
}
else if (strstr(argv[i], «/chat:») == argv[i])
{
if (action != ACTION_NONE)
return Usage();
action = ACTION_CHAT;
mbstowcs(wszSessionName, argv[i] + 6, 1024);
}
else if (strcmp(argv[i], «/sessions») == 0)
{
if (action != ACTION_NONE)
return Usage();
action = ACTION_LIST_SESSION_NAMES;
}
else if (strstr(argv[i], «/host:») == argv[i])
{
if (pcsi != 0)
return Usage();
mbstowcs(wszHostName, argv[i] + 6, 1024);
pcsi = &csi;
}
else if (strstr(argv[i], «/password:») == argv[i])
{
mbstowcs(wszPassword, argv[i] + 10, 1024);
cai.PasswordLength = wcslen(wszPassword);
}
else if (strstr(argv[i], «/user:») == argv[i])
{
if (pcai != 0 || bAnonymous)
return Usage();
char *pszDelim = strchr(argv[i] + 7, '\\');
if (pszDelim == 0)
return Usage();
*pszDelim = 0;
pszDelim++;
mbstowcs(wszDomainName, argv[i] + 6, 1024);
cai.DomainLength = wcslen(wszDomainName);
mbstowcs(wszUserName, pszDelim, 1024);
cai.UserLength = wcslen(wszUserName);
pcai = &cai;
}
}
if (action == ACTION_NONE)
return Usage();
HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);
if (FAILED(hr))
return hr;
// allow anonymous callbacks from chat server
hr = CoInitializeSecurity(0, -1, 0, 0,
RPC_C_AUTHN_LEVEL_NONE,
RPC_C_IMP_LEVEL_ANONYMOUS,
0, EOAC_NONE, 0);
if (SUCCEEDED(hr))
{
// grab the requested session manager
IChatSessionManager *pcsm = 0;
hr = CoGetClassObject(CLSID_ChatSession, CLSCTX_ALL,
pcsi, IID_IChatSessionManager,
(void**)&pcsm);
if (SUCCEEDED(hr))
{
// apply security blanket if desired
if (!bAnonymous)
hr = CoSetProxyBlanket(pcsm, RPC_C_AUTHN_WINNT,
RPC_C_AUTHZ_NONE, 0,
RPC_C_AUTHN_LEVEL_PKT,
RPC_C_IMP_LEVEL_IDENTIFY,
pcai, EOAC_NONE);
// dispatch request
switch (action)
{
case ACTION_CHAT:
Chat(wszSessionName, pcsm, pcai, bAnonymous);
break;
case ACTION_DELETE_SESSION:
Delete(wszSessionName, pcsm);
break;
case ACTION_LIST_SESSION_NAMES:
List(pcsm);
break;
default:
Usage();
}
// release session manager
pcsm->Release();
}
}
CoUninitialize();
return hr;
}
ChatSession.h
/////////////////////////////////////////////////////
//
// ChatSession.h
//
// Copyright 1997, Don Box/Addison Wesley
//
// This code accompanies the book "The Component
// Object Model" from Addison Wesley. Blah blah blah
//
//
#ifndef _CHATSESSION_H
#define _CHATSESSION_H
// this pragma shuts up the compiler warnings due to
// the pre MSC11SP1 debugger choking on long template names.
#pragma warning(disable:4786)
#define _WIN32_WINNT 0x403
#include
#include
#include
#include
using namespace std;
// bring in IDL-generated interface definitions
#include «..\include\COMChat.h»
// this class models a particular chat session
class ChatSession : public IChatSession
{
friend class StatementEnumerator;
LONG m_cRef;
CRITICAL_SECTION m_csStatementLock;
CRITICAL_SECTION m_csAdviseLock;
OLECHAR m_wszSessionName[1024];
bool m_bIsDeleted;
bool m_bAllowAnonymousAccess;
vector m_statements;
struct LISTENER
{
LISTENER *pPrev;
LISTENER *pNext;
OLECHAR *pwszUser;
IChatSessionEvents *pItf;
};
LISTENER *m_pHeadListeners;
void SLock(void);
void SUnlock(void);
void ALock(void);
void AUnlock(void);
bool CheckAccess(const OLECHAR *pwszUser);
protected:
virtual ~ChatSession(void);
void Fire_OnNewStatement(const OLECHAR *pwszUser,
const OLECHAR *pwszStatement);
void Fire_OnNewUser(const OLECHAR *pwszUser);
void Fire_OnUserLeft(const OLECHAR *pwszUser);
public:
ChatSession(const OLECHAR *pwszSessionName,
bool bAllowAnonymousAccess);
void Disconnect(void);
// IUnknown methods
STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
// IChatSession methods
STDMETHODIMP get_SessionName(OLECHAR **ppwsz);
STDMETHODIMP Say(const OLECHAR *pwszStatement);
STDMETHODIMP GetStatements(IEnumString **ppes);
STDMETHODIMP Advise(IChatSessionEvents *pEventSink,
DWORD *pdwReg);
STDMETHODIMP Unadvise(DWORD dwReg);
};
// this class enumerates the statements of a session
class StatementEnumerator : public IEnumString
{
LONG m_cRef;
ChatSession *m_pThis;
vector::iterator m_cursor;
CRITICAL_SECTION m_csLock;
protected:
void Lock(void);
void Unlock(void);
virtual ~StatementEnumerator(void);
public:
StatementEnumerator(ChatSession *pThis);
// IUnknown methods
STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
// IEnumString methods
STDMETHODIMP Next(ULONG cElems, OLECHAR **rgElems,
ULONG *pcFetched);
STDMETHODIMP Skip(ULONG cElems);
STDMETHODIMP Reset(void);
STDMETHODIMP Clone(IEnumString **ppes);
};
// this class models the management of chat sessions
// and acts as the class object for CLSID_ChatSession
class ChatSessionClass : public IChatSessionManager,
public IExternalConnection
{
friend class SessionNamesEnumerator;
typedef map SESSIONMAP;
LONG m_cStrongLocks;
SESSIONMAP m_sessions;
CRITICAL_SECTION m_csSessionLock;
void Lock(void);
void Unlock(void);
bool CheckAccess(const OLECHAR *pwszUser);
public:
virtual ~ChatSessionClass(void);
ChatSessionClass(void);
// IUnknown methods
STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
// IExternalConnection methods
STDMETHODIMP_(DWORD) AddConnection(DWORD extconn, DWORD);
STDMETHODIMP_(DWORD) ReleaseConnection(DWORD extconn, DWORD,
BOOL bLastReleaseKillsStub);
// IChatSessionManager methods
STDMETHODIMP GetSessionNames(IEnumString **ppes);
STDMETHODIMP FindSession(const OLECHAR *pwszSessionName,
BOOL bDontCreate,
BOOL bAllowAnonymousAccess,
IChatSession **ppcs);
STDMETHODIMP DeleteSession(const OLECHAR *pwszSessionName);
};
// this class enumerates the session names of a server
class SessionNamesEnumerator : public IEnumString
{
LONG m_cRef;
vector *m_pStrings;
SessionNamesEnumerator *m_pCloneSource;
vector::iterator m_cursor;
CRITICAL_SECTION m_csLock;
protected:
vector& Strings(void);
void Lock(void);
void Unlock(void);
virtual ~SessionNamesEnumerator(void);
public:
SessionNamesEnumerator(ChatSessionClass *pSessionClass);
SessionNamesEnumerator(SessionNamesEnumerator *pCloneSource);
// IUnknown methods
STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
// IEnumString methods
STDMETHODIMP Next(ULONG cElems, OLECHAR **rgElems,
ULONG *pcFetched);
STDMETHODIMP Skip(ULONG cElems);
STDMETHODIMP Reset(void);
STDMETHODIMP Clone(IEnumString **ppes);
};
#endif
ChatSession.cpp
/////////////////////////////////////////////////////
//
// ChatSession.cpp
//
// Copyright 1997, Don Box/Addison Wesley
//
// This code accompanies the book "The Component
// Object Model" from Addison Wesley. Blah blah blah
//
//
#include «ChatSession.h»
#include
// these routines are defined in svc.cpp to
// control server lifetime
extern void ModuleLock(void);
extern void ModuleUnlock(void);
// these access control objects are created
// in svc.cpp to control various privileged
// operations. Most operations in this class
// are non-privileged, so anyone can get in.
extern IAccessControl *g_pacUsers;
extern IAccessControl *g_pacAdmins;
// utility functions /////////////////////////
// duplicate an OLECHAR * using CoTaskMemAlloc
OLECHAR *OLESTRDUP(const OLECHAR *pwsz)
{
DWORD cb = sizeof(OLECHAR)*(wcslen(pwsz) + 1);
OLECHAR *pwszResult = (OLECHAR*)CoTaskMemAlloc(cb);
if (pwszResult)
wcscpy(pwszResult, pwsz);
return pwszResult;
}
// get the caller's username (or «anonymous» if
// no authentication was specified by the caller).
OLECHAR *GetCaller(void)
{
OLECHAR *pwsz = 0;
HRESULT hr = CoQueryClientBlanket(0,0,0,0,0,(void**)&pwsz,0);
if (SUCCEEDED(hr))
return OLESTRDUP(pwsz);
else
return OLESTRDUP(OLESTR(«anonymous»));
}
// class ChatSession ///////////////////////////////
ChatSession::ChatSession(const OLECHAR *pwszSessionName,
bool bAllowAnonymousAccess)
: m_cRef(0),
m_bAllowAnonymousAccess(bAllowAnonymousAccess),
m_pHeadListeners(0)
{
wcscpy(m_wszSessionName, pwszSessionName);
InitializeCriticalSection(&m_csStatementLock);
InitializeCriticalSection(&m_csAdviseLock);
}
ChatSession::~ChatSession(void)
{
DeleteCriticalSection(&m_csStatementLock);
DeleteCriticalSection(&m_csAdviseLock);
// tear down connected listeners
while (m_pHeadListeners)
{
LISTENER *pThisNode = m_pHeadListeners;
if (pThisNode->pItf)
pThisNode->pItf->Release();
if (pThisNode->pwszUser)
CoTaskMemFree(pThisNode->pwszUser);
m_pHeadListeners = pThisNode->pNext;
delete pThisNode;
}
}
// helper methods ///////////
void ChatSession::Disconnect(void)
{
CoDisconnectObject(this, 0);
// tear down connected listeners
ALock();
while (m_pHeadListeners)
{
LISTENER *pThisNode = m_pHeadListeners;
if (pThisNode->pItf)
pThisNode->pItf->Release();
if (pThisNode->pwszUser)
CoTaskMemFree(pThisNode->pwszUser);
m_pHeadListeners = pThisNode->pNext;
delete pThisNode;
}
AUnlock();
}
// send the OnNewStatement event to all listeners
void
ChatSession::Fire_OnNewStatement(const OLECHAR *pwszUser,
const OLECHAR *pwszStatement)
{
ALock();
for (LISTENER *pNode = m_pHeadListeners;
pNode != 0; pNode = pNode->pNext)
{
if (pNode->pItf)
pNode->pItf->OnNewStatement(pwszUser, pwszStatement);
}
AUnlock();
}
// send the OnNewUser event to all listeners
void
ChatSession::Fire_OnNewUser(const OLECHAR *pwszUser)
{
ALock();
for (LISTENER *pNode = m_pHeadListeners;
pNode != 0; pNode = pNode->pNext)
{
if (pNode->pItf)
pNode->pItf->OnNewUser(pwszUser);
}
AUnlock();
}
// send the OnUserLeft event to all listeners
void
ChatSession::Fire_OnUserLeft(const OLECHAR *pwszUser)
{
ALock();
for (LISTENER *pNode = m_pHeadListeners;
pNode != 0; pNode = pNode->pNext)
{
if (pNode->pItf)
pNode->pItf->OnUserLeft(pwszUser);
}
AUnlock();
}
// lock wrappers
void ChatSession::SLock(void)
{
EnterCriticalSection(&m_csStatementLock);
}
void ChatSession::SUnlock(void)
{
LeaveCriticalSection(&m_csStatementLock);
}
void ChatSession::ALock(void)
{
EnterCriticalSection(&m_csAdviseLock);
}
void ChatSession::AUnlock(void)
{
LeaveCriticalSection(&m_csAdviseLock);
}
// helper method to check access to Say method
bool
ChatSession::CheckAccess(const OLECHAR *pwszUser)
{
if (wcscmp(pwszUser, L"anonymous") == 0)
return m_bAllowAnonymousAccess;
// form trustee from caller and use Access Control
// object hardwired to COMChat Users group
TRUSTEEW trustee = {
0, NO_MULTIPLE_TRUSTEE, TRUSTEE_IS_NAME,
TRUSTEE_IS_USER,
const_cast(pwszUser)
};
BOOL bIsAllowed;
HRESULT hr = g_pacUsers->IsAccessAllowed(&trustee,0,
COM_RIGHTS_EXECUTE,
&bIsAllowed);
return SUCCEEDED(hr) && bIsAllowed != FALSE;
}
// IUnknown methods
STDMETHODIMP
ChatSession::QueryInterface(REFIID riid, void **ppv)
{
if (riid == IID_IUnknown)
*ppv = static_cast(this);
else if (riid == IID_IChatSession)
*ppv = static_cast(this);
else
return (*ppv = 0), E_NOINTERFACE;
reinterpret_cast(*ppv)->AddRef();
return S_OK;
}
STDMETHODIMP_(ULONG)
ChatSession::AddRef(void)
{
ModuleLock();
return InterlockedIncrement(&m_cRef);
}
STDMETHODIMP_(ULONG)
ChatSession::Release(void)
{
LONG res = InterlockedDecrement(&m_cRef);
if (res == 0)
delete this;
ModuleUnlock();
return res;
}
// IChatSession methods
STDMETHODIMP
ChatSession::get_SessionName(OLECHAR **ppwsz)
{
if (!ppwsz)
return E_INVALIDARG;
else if ((*ppwsz = OLESTRDUP(m_wszSessionName)) == 0)
return E_OUTOFMEMORY;
return S_OK;
}
STDMETHODIMP
ChatSession::Say(const OLECHAR *pwszStatement)
{
HRESULT hr = S_OK;
// protect access to method
OLECHAR *pwszUser = GetCaller();
if (pwszUser && CheckAccess(pwszUser))
{
SLock();
try
{
wstring s = pwszUser;
s += L":";
s += pwszStatement;
m_statements.push_back(s);
}
catch(...)
{
hr = E_OUTOFMEMORY;
}
SUnlock();
if (SUCCEEDED(hr))
Fire_OnNewStatement(pwszUser, pwszStatement);
}
else
hr = E_ACCESSDENIED;
CoTaskMemFree(pwszUser);
return hr;
}
STDMETHODIMP
ChatSession::GetStatements(IEnumString **ppes)
{
if (ppes == 0)
return E_INVALIDARG;
*ppes = new StatementEnumerator(this);
if (*ppes == 0)
return E_OUTOFMEMORY;
(*ppes)->AddRef();
return S_OK;
}
STDMETHODIMP
ChatSession::Advise(IChatSessionEvents *pEventSink,
DWORD *pdwReg)
{
HRESULT hr = S_OK;
if (pEventSink == 0 || pdwReg == 0)
return E_INVALIDARG;
LISTENER *pNew = new LISTENER;
if (pNew == 0)
return E_OUTOFMEMORY;
OLECHAR *pwszUser = GetCaller();
if (pwszUser)
{
Fire_OnNewUser(pwszUser);
ALock();
pNew->pwszUser = pwszUser;
if (pNew->pItf = pEventSink)
pEventSink->AddRef();
pNew->pNext = m_pHeadListeners;
if (m_pHeadListeners)
m_pHeadListeners->pPrev = pNew;
pNew->pPrev = 0;
m_pHeadListeners = pNew;
AUnlock();
}
else
{
delete pNew;
return E_OUTOFMEMORY;
}
*pdwReg = reinterpret_cast(pNew);
return hr;
}
STDMETHODIMP
ChatSession::Unadvise(DWORD dwReg)
{
if (dwReg == 0)
return E_INVALIDARG;
HRESULT hr = S_OK;
LISTENER *pThisNode = reinterpret_cast(dwReg);
ALock();
if (pThisNode->pPrev)
pThisNode->pPrev->pNext = pThisNode->pNext;
else
m_pHeadListeners = pThisNode->pNext;
if (pThisNode->pNext)
pThisNode->pNext->pPrev = pThisNode->pPrev;
if (pThisNode->pItf)
pThisNode->pItf->Release();
OLECHAR *pwszUser = pThisNode->pwszUser;
delete pThisNode;
AUnlock();
Fire_OnUserLeft(pwszUser);
CoTaskMemFree(pwszUser);
return hr;
}
// class StatementEnumerator ///////////////////
StatementEnumerator::StatementEnumerator(ChatSession *pThis)
: m_cRef(0),
m_pThis(pThis),
m_cursor(pThis->m_statements.begin())
{
m_pThis->AddRef();
InitializeCriticalSection(&m_csLock);
}
StatementEnumerator::~StatementEnumerator(void)
{
m_pThis->Release();
DeleteCriticalSection(&m_csLock);
}
// lock helpers (note that ChatSession is locked
// simultaneously)
void
StatementEnumerator::Lock(void)
{
EnterCriticalSection(&m_csLock);
m_pThis->SLock();
}
void
StatementEnumerator::Unlock(void)
{
LeaveCriticalSection(&m_csLock);
m_pThis->SUnlock();
}
// IUnknown methods
STDMETHODIMP
StatementEnumerator::QueryInterface(REFIID riid, void **ppv)
{
if (riid == IID_IUnknown)
*ppv = static_cast(this);
else if (riid == IID_IEnumString)
*ppv = static_cast(this);
else
return (*ppv = 0), E_NOINTERFACE;
reinterpret_cast(*ppv)->AddRef();
return S_OK;
}
STDMETHODIMP_(ULONG)
StatementEnumerator::AddRef(void)
{
return InterlockedIncrement(&m_cRef);
}
STDMETHODIMP_(ULONG)
StatementEnumerator::Release(void)
{
LONG res = InterlockedDecrement(&m_cRef);
if (res == 0)
delete this;
return res;
}
// IEnumString methods
STDMETHODIMP
StatementEnumerator::Next(ULONG cElems, OLECHAR **rgElems,
ULONG *pcFetched)
{
if (pcFetched == 0 && cElems > 1)
return E_INVALIDARG;
ZeroMemory(rgElems, sizeof(OLECHAR*) * cElems);
Lock();
ULONG cActual = 0;
while (cActual < cElems
&& m_cursor != m_pThis->m_statements.end())
{
if (rgElems[cActual] = OLESTRDUP((*m_cursor).c_str()))
{
m_cursor++;
cActual++;
}
else // allocation error, unwind
{
while (cActual > 0)
{
cActual–;
CoTaskMemFree(rgElems[cActual]);
rgElems[cActual] = 0;
}
break;
}
}
Unlock();
if (pcFetched)
*pcFetched = cActual;
return cElems == cActual ? S_OK : S_FALSE;
}
STDMETHODIMP
StatementEnumerator::Skip(ULONG cElems)
{
Lock();
ULONG cActual = 0;
while (cActual < cElems
&& m_cursor != m_pThis->m_statements.end())
{
m_cursor++;
cActual++;
}
Unlock();
return cElems == cActual ? S_OK : S_FALSE;
}
STDMETHODIMP
StatementEnumerator::Reset(void)
{
Lock();
m_cursor = m_pThis->m_statements.begin();
Unlock();
return S_OK;
}
STDMETHODIMP
StatementEnumerator::Clone(IEnumString **ppes)
{
if (ppes == 0)
return E_INVALIDARG;
if (*ppes = new StatementEnumerator(m_pThis))
return S_OK;
return E_OUTOFMEMORY;
}
// class ChatSessionClass /////////////////////
ChatSessionClass::ChatSessionClass(void)
: m_cStrongLocks(0)
{
InitializeCriticalSection(&m_csSessionLock);
}
ChatSessionClass::~ChatSessionClass(void)
{
DeleteCriticalSection(&m_csSessionLock);
}
void
ChatSessionClass::Lock(void)
{
EnterCriticalSection(&m_csSessionLock);
}
void
ChatSessionClass::Unlock(void)
{
LeaveCriticalSection(&m_csSessionLock);
}
// helper method to protect access to DeleteSession
// to only allow COMChat Admins to delete groups
bool
ChatSessionClass::CheckAccess(const OLECHAR *pwszUser)
{
if (wcscmp(pwszUser, L"anonymous") == 0)
return false;
TRUSTEEW trustee = {
0, NO_MULTIPLE_TRUSTEE, TRUSTEE_IS_NAME,
TRUSTEE_IS_USER, const_cast(pwszUser)
};
BOOL bIsAllowed;
HRESULT hr = g_pacAdmins->IsAccessAllowed(&trustee,0,
COM_RIGHTS_EXECUTE,
&bIsAllowed);
if (FAILED(hr))
bIsAllowed = false;
return SUCCEEDED(hr) && bIsAllowed != FALSE;
}
// IUnknown methods
STDMETHODIMP
ChatSessionClass::QueryInterface(REFIID riid, void **ppv)
{
if (riid == IID_IUnknown)
*ppv = static_cast(this);
else if (riid == IID_IChatSessionManager)
*ppv = static_cast(this);
else if (riid == IID_IExternalConnection)
*ppv = static_cast(this);
else
return (*ppv = 0), E_NOINTERFACE;
reinterpret_cast(*ppv)->AddRef();
return S_OK;
}
STDMETHODIMP_(ULONG)
ChatSessionClass::AddRef(void)
{
return 2;
}
STDMETHODIMP_(ULONG)
ChatSessionClass::Release(void)
{
return 1;
}
// IExternalConnection methods
STDMETHODIMP_(DWORD)
ChatSessionClass::AddConnection(DWORD extconn, DWORD)
{
if (extconn & EXTCONN_STRONG)
{
ModuleLock();
return InterlockedIncrement(&m_cStrongLocks);
}
return 0;
}
STDMETHODIMP_(DWORD)
ChatSessionClass::ReleaseConnection(DWORD extconn, DWORD,
BOOL bLastReleaseKillsStub)
{
if (extconn & EXTCONN_STRONG)
{
LONG res = InterlockedDecrement(&m_cStrongLocks);
if (res == 0 && bLastReleaseKillsStub)
CoDisconnectObject(
static_cast(this), 0);
ModuleUnlock();
return res;
}
return 0;
}
// IChatSessionManager methods
STDMETHODIMP
ChatSessionClass::GetSessionNames(IEnumString **ppes)
{
if (ppes == 0)
return E_INVALIDARG;
if (*ppes = new SessionNamesEnumerator(this))
{
(*ppes)->AddRef();
return S_OK;
}
else
return E_OUTOFMEMORY;
}
STDMETHODIMP
ChatSessionClass::FindSession(const OLECHAR *pwszSessionName,
BOOL bDontCreate,
BOOL bAllowAnonymousAccess,
IChatSession **ppcs)
{
if (ppcs == 0)
return E_INVALIDARG;
HRESULT hr = E_FAIL;
*ppcs = 0;
OLECHAR *pwszUser = GetCaller();
Lock();
SESSIONMAP::iterator it = m_sessions.find(pwszSessionName);
if (it == m_sessions.end())
{
if (bDontCreate)
hr = E_FAIL;
else if (!bAllowAnonymousAccess
&& wcscmp(pwszUser, L"anonymous") == 0)
hr = E_ACCESSDENIED;
else
{
ChatSession *pNew =
new ChatSession(pwszSessionName,
bAllowAnonymousAccess != FALSE);
if (pNew)
{
pNew->AddRef();
m_sessions.insert(
pairChatSession*>(pwszSessionName,
pNew));
(*ppcs = pNew)->AddRef();
hr = S_OK;
}
else
hr = E_OUTOFMEMORY;
}
}
else
{
(*ppcs = (*it).second)->AddRef();
hr = S_OK;
}
Unlock();
CoTaskMemFree(pwszUser);
return hr;
}
STDMETHODIMP
ChatSessionClass::DeleteSession(const OLECHAR *pwszSessionName)
{
if (pwszSessionName == 0)
return E_INVALIDARG;
HRESULT hr = E_FAIL;
OLECHAR *pwszUser = GetCaller();
if (CheckAccess(pwszUser))
{
Lock();
SESSIONMAP::iterator it
= m_sessions.find(pwszSessionName);
if (it == m_sessions.end())
{
hr = E_FAIL;
}
else
{
(*it).second->Disconnect();
(*it).second->Release();
m_sessions.erase(it);
hr = S_OK;
}
Unlock();
}
else
hr = E_ACCESSDENIED;
CoTaskMemFree(pwszUser);
return hr;
}
// class SessionNamesEnumerator
vector&
SessionNamesEnumerator::Strings(void)
{
if (m_pStrings)
return *m_pStrings;
else
return *(m_pCloneSource->m_pStrings);
}
void
SessionNamesEnumerator::Lock(void)
{
EnterCriticalSection(&m_csLock);
}
void
SessionNamesEnumerator::Unlock(void)
{
LeaveCriticalSection(&m_csLock);
}
SessionNamesEnumerator::SessionNamesEnumerator(
ChatSessionClass *pSessionClass)
: m_cRef(0),
m_pStrings(0),
m_pCloneSource(0)
{
typedef ChatSessionClass::SESSIONMAP::iterator iterator;
ChatSessionClass::SESSIONMAP &sessions
= pSessionClass->m_sessions;
m_pStrings = new vector;
pSessionClass->Lock();
for (iterator it = sessions.begin();
it != sessions.end();
it++)
{
m_pStrings->push_back((*it).first);
}
pSessionClass->Unlock();
m_cursor = Strings().begin();
InitializeCriticalSection(&m_csLock);
}
SessionNamesEnumerator::SessionNamesEnumerator(
SessionNamesEnumerator *pCloneSource)
: m_cRef(0),
m_pStrings(0),
m_pCloneSource(pCloneSource)
{
m_pCloneSource->AddRef();
m_cursor = Strings().begin();
InitializeCriticalSection(&m_csLock);
}
SessionNamesEnumerator::~SessionNamesEnumerator(void)
{
if (m_pCloneSource)
m_pCloneSource->Release();
else if (m_pStrings)
delete m_pStrings;
DeleteCriticalSection(&m_csLock);
}
// IUnknown methods
STDMETHODIMP
SessionNamesEnumerator::QueryInterface(REFIID riid, void **ppv)
{
if (riid == IID_IUnknown)
*ppv = static_cast(this);
else if (riid == IID_IEnumString)
*ppv = static_cast(this);
else
return (*ppv = 0), E_NOINTERFACE;
reinterpret_cast(*ppv)->AddRef();
return S_OK;
}
STDMETHODIMP_(ULONG)
SessionNamesEnumerator::AddRef(void)
{
ModuleLock();
return InterlockedIncrement(&m_cRef);
}
STDMETHODIMP_(ULONG)
SessionNamesEnumerator::Release(void)
{
LONG res = InterlockedDecrement(&m_cRef);
if (res == 0)
delete this;
ModuleUnlock();
return res;
}
// IEnumString methods
STDMETHODIMP
SessionNamesEnumerator::Next(ULONG cElems, OLECHAR **rgElems,
ULONG *pcFetched)
{
if (cElems > 1 && pcFetched == 0)
return E_INVALIDARG;
ULONG cActual = 0;
vector &rstrings = Strings();
Lock();
while (cActual < cElems
&& m_cursor != rstrings.end())
{
if (rgElems[cActual] = OLESTRDUP((*m_cursor).c_str()))
{
m_cursor++;
cActual++;
}
else // allocation error, unwind
{
while (cActual > 0)
{
cActual–;
CoTaskMemFree(rgElems[cActual]);
rgElems[cActual] = 0;
}
break;
}
}
Unlock();
if (cActual)
*pcFetched = cActual;
return cActual == cElems ? S_OK : S_FALSE;
}
STDMETHODIMP
SessionNamesEnumerator::Skip(ULONG cElems)
{
ULONG cActual = 0;
vector &rstrings = Strings();
Lock();
while (cActual < cElems
&& m_cursor != rstrings.end())
{
m_cursor++;
cActual++;
}
Unlock();
return cActual == cElems ? S_OK : S_FALSE;
}
STDMETHODIMP
SessionNamesEnumerator::Reset(void)
{
Lock();
m_cursor = Strings().begin();
Unlock();
return S_OK;
}
STDMETHODIMP
SessionNamesEnumerator::Clone(IEnumString **ppes)
{
if (ppes == 0)
return E_INVALIDARG;
SessionNamesEnumerator *pCloneSource = m_pCloneSource;
if (pCloneSource == 0) // we are the source
m_pCloneSource = this;
*ppes = new SessionNamesEnumerator(pCloneSource);
if (*ppes)
{
(*ppes)->AddRef();
return S_OK;
}
return E_OUTOFMEMORY;
}
svc.cpp
/////////////////////////////////////////////////////
//
// svc.cpp
//
// Copyright 1997, Don Box/Addison Wesley
//
// This code accompanies the book "The Component
// Object Model" from Addison Wesley. Blah blah blah
//
//
#define _WIN32_WINNT 0x403
#include
#include
#include
#include
#include «ChatSession.h»
#include «../include/COMChat_i.c»
#if !defined(HAVE_IID_IACCESSCONTROL)
// there is a common bug is the SDK headers and libs
// that causes IID_IAccessControl to be undefined.
// We define it here to give the GUID linkage.
DEFINE_GUID(IID_IAccessControl,0xEEDD23E0, 0x8410, 0x11CE,
0xA1, 0xC3, 0x08, 0x00, 0x2B, 0x2B, 0x8D, 0x8F);
#endif
// standard MTA lifetime management helpers
HANDLE g_heventDone = CreateEvent(0, TRUE, FALSE, 0);
void ModuleLock(void)
{
CoAddRefServerProcess();
}
void ModuleUnlock(void)
{
if (CoReleaseServerProcess() == 0)
SetEvent(g_heventDone);
}
// standard self-registration table
const char *g_RegTable[][3] = {
{ «CLSID\\{5223A053-2441-11d1-AF4F-0060976AA886}»,
0, «ChatSession» },
{ «CLSID\\{5223A053-2441-11d1-AF4F-0060976AA886}»,
«AppId», «{5223A054-2441-11d1-AF4F-0060976AA886}»
},
{ «CLSID\\{5223A053-2441-11d1-AF4F-0060976AA886}\\LocalServer32»,
0, (const char*)-1 // rogue value indicating file name
},
{ «AppID\\{5223A054-2441-11d1-AF4F-0060976AA886}»,
0, «ChatSession Server» },
{ «AppID\\{5223A054-2441-11d1-AF4F-0060976AA886}»,
«RunAs», «Domain\\ReplaceMe»
},
{ «AppID\\{5223A054-2441-11d1-AF4F-0060976AA886}»,
«Chat Admins Group», «Domain\\ReplaceMe»
},
{ «AppID\\{5223A054-2441-11d1-AF4F-0060976AA886}»,
«Chat Users Group», «Domain\\ReplaceMe»
},
{ «AppID\\COMChat.exe»,
«AppId», «{5223A054-2441-11d1-AF4F-0060976AA886}»
},
};
// self-unregistration routine
STDAPI UnregisterServer(void) {
HRESULT hr = S_OK;
int nEntries = sizeof(g_RegTable)/sizeof(*g_RegTable);
for (int i = nEntries – 1; i >= 0; i–){
const char *pszKeyName = g_RegTable[i][0];
long err = RegDeleteKeyA(HKEY_CLASSES_ROOT, pszKeyName);
if (err != ERROR_SUCCESS)
hr = S_FALSE;
}
return hr;
}
// self-registration routine
STDAPI RegisterServer(HINSTANCE hInstance = 0) {
HRESULT hr = S_OK;
// look up server's file name
char szFileName[MAX_PATH];
GetModuleFileNameA(hInstance, szFileName, MAX_PATH);
// register entries from table
int nEntries = sizeof(g_RegTable)/sizeof(*g_RegTable);
for (int i = 0; SUCCEEDED(hr) && i < nEntries; i++) {
const char *pszKeyName = g_RegTable[i][0];
const char *pszValueName = g_RegTable[i][1];
const char *pszValue = g_RegTable[i][2];
// map rogue value to module file name
if (pszValue == (const char*)-1)
pszValue = szFileName;
HKEY hkey;
// create the key
long err = RegCreateKeyA(HKEY_CLASSES_ROOT,
pszKeyName, &hkey);
if (err == ERROR_SUCCESS) {
// set the value
err = RegSetValueExA(hkey, pszValueName, 0,
REG_SZ, (const BYTE*)pszValue,
(strlen(pszValue) + 1));
RegCloseKey(hkey);
}
if (err != ERROR_SUCCESS) {
// if cannot add key or value, back out and fail
UnregisterServer();
hr = SELFREG_E_CLASS;
}
}
return hr;
}
// these point to standard access control objects
// used to protect particular methods
IAccessControl *g_pacUsers = 0;
IAccessControl *g_pacAdmins = 0;
// this routine is called at process init time
// to build access control objects and to allow
// anonymous access to server by default
HRESULT InitializeApplicationSecurity(void)
{
// load groupnames from registry
static OLECHAR wszAdminsGroup[1024];
static OLECHAR wszUsersGroup[1024];
HKEY hkey;
long err = RegOpenKeyEx(HKEY_CLASSES_ROOT,
__TEXT(«AppID\\{5223A054-2441-11d1-AF4F-0060976AA886}»),
0, KEY_QUERY_VALUE,
&hkey);
if (err == ERROR_SUCCESS)
{
DWORD cb = sizeof(wszAdminsGroup);
err = RegQueryValueExW(hkey, L"Chat Admins Group",
0, 0, (BYTE*)wszAdminsGroup,
&cb);
cb = sizeof(wszAdminsGroup);
if (err == ERROR_SUCCESS)
err = RegQueryValueExW(hkey,
L"Chat Users Group",
0, 0, (BYTE*)wszUsersGroup,
&cb);
RegCloseKey(hkey);
}
if (err != ERROR_SUCCESS)
return MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32,
GetLastError());
// declare vectors of user/groups for 2 access
// control objects
ACTRL_ACCESS_ENTRYW rgaaeUsers[] = {
{ {0, NO_MULTIPLE_TRUSTEE, TRUSTEE_IS_NAME,
TRUSTEE_IS_GROUP, wszUsersGroup },
ACTRL_ACCESS_ALLOWED, COM_RIGHTS_EXECUTE, 0,
NO_INHERITANCE, 0 },
};
ACTRL_ACCESS_ENTRY_LISTW aaelUsers = {
sizeof(rgaaeUsers)/sizeof(*rgaaeUsers),
rgaaeUsers
};
ACTRL_PROPERTY_ENTRYW apeUsers = { 0, &aaelUsers, 0 };
ACTRL_ACCESSW aaUsers = { 1, &apeUsers };
ACTRL_ACCESS_ENTRYW rgaaeAdmins[] = {
{ {0, NO_MULTIPLE_TRUSTEE, TRUSTEE_IS_NAME,
TRUSTEE_IS_GROUP, wszAdminsGroup },
ACTRL_ACCESS_ALLOWED, COM_RIGHTS_EXECUTE, 0,
NO_INHERITANCE, 0 },
};
ACTRL_ACCESS_ENTRY_LISTW aaelAdmins = {
sizeof(rgaaeAdmins)/sizeof(*rgaaeAdmins),
rgaaeAdmins
};
ACTRL_PROPERTY_ENTRYW apeAdmins = { 0, &aaelAdmins, 0 };
ACTRL_ACCESSW aaAdmins = { 1, &apeAdmins };
HRESULT hr = CoInitializeSecurity(0, -1, 0, 0,
RPC_C_AUTHN_LEVEL_NONE,
RPC_C_IMP_LEVEL_ANONYMOUS,
0,
EOAC_NONE,
0);
if (SUCCEEDED(hr))
{
hr = CoCreateInstance(CLSID_DCOMAccessControl,
0, CLSCTX_ALL, IID_IAccessControl,
(void**)&g_pacUsers);
if (SUCCEEDED(hr))
hr = g_pacUsers->SetAccessRights(&aaUsers);
if (SUCCEEDED(hr))
{
hr = CoCreateInstance(CLSID_DCOMAccessControl,
0, CLSCTX_ALL,
IID_IAccessControl,
(void**)&g_pacAdmins);
if (SUCCEEDED(hr))
hr = g_pacAdmins->SetAccessRights(&aaAdmins);
}
if (FAILED(hr))
{
if (g_pacAdmins)
{
g_pacAdmins->Release();
g_pacAdmins = 0;
}
if (g_pacUsers)
{
g_pacUsers->Release();
g_pacUsers = 0;
}
}
}
return hr;
}
// the main thread routine that simply registers the class
// object and waits to die
int WINAPI WinMain(HINSTANCE, HINSTANCE,
LPSTR szCmdParam, int)
{
const TCHAR *pszPrompt =
__TEXT("Ensure that you have properly ")
__TEXT("configured the application to ")
__TEXT("run as a particular user and that ")
__TEXT("you have manually changed the ")
__TEXT("Users and Admins Group registry ")
__TEXT(«settings under this server's AppID.»);
HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);
if (FAILED(hr))
return hr;
// look for self-registration flags
if (strstr(szCmdParam, «/UnregServer») != 0
|| strstr(szCmdParam, «-UnregServer») != 0)
{
hr = UnregisterServer();
CoUninitialize();
return hr;
}
else if (strstr(szCmdParam, «/RegServer») != 0
|| strstr(szCmdParam, «-RegServer») != 0)
{
hr = RegisterServer();
MessageBox(0, pszPrompt, __TEXT(«COMChat»),
MB_SETFOREGROUND);
CoUninitialize();
return hr;
}
// set up process security
hr = InitializeApplicationSecurity();
if (SUCCEEDED(hr))
{
// register class object and wait to die
DWORD dwReg;
static ChatSessionClass cmc;
hr = CoRegisterClassObject(CLSID_ChatSession,
static_cast(&cmc),
CLSCTX_LOCAL_SERVER
REGCLS_SUSPENDED|REGCLS_MULTIPLEUSE,
&dwReg);
if (SUCCEEDED(hr))
{
hr = CoResumeClassObjects();
if (SUCCEEDED(hr))
WaitForSingleObject(g_heventDone, INFINITE);
CoRevokeClassObject(dwReg);
}
g_pacUsers->Release();
g_pacAdmins->Release();
}
if (FAILED(hr))
MessageBox(0, pszPrompt, __TEXT(«Error»),
MB_SETFOREGROUND);
CoUninitialize();
return 0;
}
More Book Stuff
Source Code
COM Chat Compilable versions of the source code in Appendix B of the book.
YACL Yet another COM library. Contains the various macros and C++ classes described in the book.
IGlobalInterfaceTable Wrapper/Sample A simplification of apartment-independent pointer use.
HostHook A custom channel hook that allows you to find out the caller's and callee's network address.
APTSURA custom surrogate that spawns distinct STA threads for each activation request.
Custom Moniker StuffI worked with some friends on a custom moniker framework. Here is some of the 1st bits and pieces.
Yet Another COM Library (YACL) – Preview
This is my first drop. It contains a family of preprocessor macros that automate the boilerplate activities used in COM programming.
Please send comments and bug reports to cbbugs@braintrust.com
The freshest drop is always available at
http://www.develop.com/dbox/yacl.zip
Design Goals (in Order)
Easily used without Wizard support
Easily kept in one's head
Modular (use only what you need and nothing else)
Extensible
Small Code
Fast Code
No DLL ever
Compiler-friendly
Platform-neutral where possible (including 16-bit Windows)
Current Feature Set
Anal-rententive Smart Pointer
Efficient and intuitive Unicode handling
Table-driven QueryInterface
Table-driven Registration
Table-driven Class management
Generic Class Factory implementation.
Preprocessor macros for de facto IUnknown implementation techniques.
Preprocessor macros for de facto module management routines.
Preprocessor macros for de facto DllXXX routines.
Preprocessor macros for de facto out-of-proc CRCO/Wait/CRCO sequence.
Planned Work
Performance/size tuning
Compiler/Platform testing
Verify ATL/MFC interoperation
Macro-ization of smart pointer for 16-bit windows
Add optional exception semantics to smart pointer
Map COM hresults/exception to C++ exceptions
Add support for IDispatch and friends
Add support for IConnectionPoint and friends
Add IEnum -> stl thunks.
<< |
Источник: Дональд Бокс. Сущность технологии СОМ. Библиотека программиста. 2001 {original}

Еще по теме Рассмотрим идеальный IDL-прототип метода:

  1. ИДЕАЛЬНЫХ ТИПОВ МЕТОД
  2. ИДЕАЛЬНОЕ
  3. ИДЕАЛЬНОЕ
  4. ИДЕАЛЬНЫЙ ТИП
  5. Идеальные образы
  6. Глава 5. КТО ОН, «ИДЕАЛЬНЫЙ ТИМБИЛДЕР»
  7. ИДЕАЛЬНЫЙ ТИП
  8. Идеальные нормативы
  9. Тема 43. ИДЕАЛЬНОЕ
  10. § 3. Проблема идеального
  11. ИДЕАЛЬНОЕ