Разбирая автоматизацию OLE
Автор Бин Ли
Перевод Константин Лазарев
Введение
Хорошо известен факт, что для изучения и понимания технологии компонентной модели объектов Microsoft (COM - Component Object Model), требуется по меньшей мере несколько месяцев. Даже лучшие программисты и технологи в индустрии программного обеспечения знают это и приложили много усилий в последнии годы, чтобы упростить и донести до рядовых программистов (таких как Вы и я) все прелести и идеи COM. Тем не менее, даже при наличии большого количества имеющихся в данное время публкаций, приобретение опыта эффективного программирования COM или даже просто получение пользы от использования COM в Ваших приложениях требует значительных инвестиций, временных затрат и усилий.
Одной из причин больших затрат времени на изучение COM является тот факт, что COM включает в себя широкий спектр концепций и "суб-технологий", простирающихся от простейших органов управления ActiveX до "страшно запутанных" тем, таких как унифицированная передача данных (Uniform Data Transfer), связывание и встраивание объектов (Object Linking and Embedding), автоматизация (Automation), распределенная (Distributed) COM (DCOM) и т.д. В силу всего этого вовсе не очевидно какой путь изучения следует избрать для получения немедленной пользы от COM и использования его в Вашем бизнесе.
Очень простой и эффективный способ приступить к изучению COM состоит в том, чтобы начать с очень простой и, в то же время, очень мощной технологии COM, называемой Автоматизация OLE (OLE Automation) (или короче - автоматизация). Я говорю "простой" потому, что имеется масса документации по автоматизации, а также программ из реальной жизни, которые являются "готовыми к автоматизации" и которые могут служить прекрасными примерами для изучения. Я говорю "мощный" потому, что автоматизация является наиболее мощным методом, используемым сегодня для разработки промышленных подсистем (чаще подсистемы среднего уровня) для бизнеса (большинство из них любимы нами), работающих на платформе Windows. COM не только широко используется - она используется эффективно!
Цель этой статьи показать Вам в деталях, что из себя представляет автоматизация и как это относится к COM. В процессе изложения я буду пытаться давать разные идеи о внутреннем устройстве COM, что позволит изучить множество важных концепций COM вообще в дополнение к изучению автоматизации. В конце концов моими целями являются:
Автоматизация в двух словах
Автоматизация является очень хорошим примером эффективного использования старой идеи. Упрощая: если кто-то уже сделал это, то не стоит заново изобретать колесо, когда у Вас имеется возможность использовать имеющуюся функциональность к Вашей пользе. Механизм доступности функциональности для многократного использования ее снова и снова является основным в автоматизации (и COM в целом). Я должен подчеркнуть, что повторное (многократное) использование не означает простого повторного включения программного кода. Повторное использование означает также повторное межпрограммное использование функциональности с возможностью предоставления Вашим приложением своей функциональности другим приложениям. Повторное использование означает также языковую независимость функциональности. Например, скажем Вы написали некоторый функциональный блок на C++, а использовать его можно из программ, написанных на Delphi, Visual Basic, C++ и др.
Автоматизация предоставляет также прекрасный механизм для функционального разделения Вашего приложения на логические части или слои. В наш век компьютеров приложения (в особенности критические или информационно-ориентированные приложения и подсистемы) часто разрабатываются как единый исполняемый модуль. В то же время меньшие подсистемы (каждая в виде исполняемого модуля) раздельно строются для совместной работы в целом по объективным причинам получения приемлемых сопровождаемости, масштабируемости, повторного использования и расширяемости. Разработка меньших подсистем (или даже их частей) является как раз тем самым, что может быть легко и просто выполнено с помощью автоматизации. COM также поддерживает концепцию прозрачности размещения для подсистем, основанных на COM/автоматизации. Если сказать проще, то это означает, что подсистема, разбитая на части, может быть перемещена в любое место Вашей сети. Она может просто запускаться локально на Вашем компьютере, а может работать на другом компьютере на другой стороне Земли, оставаясь в то же время цельным приложением вне зависимости от факта, что какие-то из подсистем работают не локально.
Идея, заложенная в автоматизацию, включает разработку приложений, функциональность которых может быть доступна и другим приложениям, и разработку приложений, которые "знают" как использовать функциональность, предоставляемую Вам другими приложениями. Если говорить техническими словами, приложение, которое предоставляет некоторую повторно используемую функциональность называется сервером автоматизации (automation server) (также часто называемым сервером COM), а приложение, использующее функциональность, предоставляемую сервером автоматизации, называется клиентом автоматизации (automation client) (также часто называемым контроллером автоматизации). Важно подчеркнуть, что сервер автоматизации может не быть "чистым" сервером автоматизации, также как и клиент автоматизации может не быть "чистым" клиентом автоматизации. В действительности сервер автоматизации, использующий сервисы другого сервера автоматизации, является как сервером, так и клиентом одновременно. Клиент автоматизации, предоставляющий свои сервисы другому клиенту автоматизации, также является как клиентом, так и сервером автоматизации. Глубинные механизмы (сетевые и транспортные протоколы), с помощью которых клиент автоматизации взаимодействует с сервером, уже является частью собственно COM, что в свою очередь является основой для разработки клиент-серверных приложений, основанных на автоматизации/COM.
Внутри сервера автоматизации
Сервер автоматизации - это просто двоичный исполняемый модуль, который может состоять из нескольких объектов автоматизации. Объект автоматизации (также называемый объектом COM, хотя технически объект автоматизации является объектом COM особого сорта) - это отдельный, самодостаточный объект, спроектированный для выполнения специфической задачи или функции очень похоже на "объект" в терминологии объектно-ориентированного программирования (ООП). В общем все объекты автоматизации, собранные в одном сервере автоматизации, предназначены для осуществления каких-то функциональных возможностей. Например, Microsoft Excel является сервером автоматизации, состоящим из нескольких меньших серверов автоматизации (Workbook - книга, Chart - диаграмма, Worksheet - лист, Range- диапазон и т.д.), каждый из которых представляет часть того, что Excel может делать. Идея заключается в том, что сервер автоматизации "позволяет" своим клиентам получать доступ и использовать свои объекты так же легко и просто, как-будто это его внутренние объекты. Следующий рисунок иллюстрирует эту идею:

Сервера автоматизации могут создаваться в виде DLL библиотек (иногда называемых внутренними серверами, так как DLL выполняется "в адресном пространстве клиента автоматизации") или в виде модулей EXE (иногда называемых внешними (out-of-process) серверами, так как EXE выполняются в отдельном/различном/"внешнем" процессе по отношению к клиенту автоматизации).
Некоторые преимущества создания DLL-серверов:
Некоторые преимущества создания серверов EXE:
Сделав такое вступление, давайте рассмотрим простой сервер автоматизации на Delphi. Следующий фрагмент показывает реализацию объекта автоматизации, сгенерированную при создании библиотеки ActiveX (ActiveX Library) (File | New | ActiveX | ActiveX Library. Термин ActiveX - это просто маркетинговый термин Microsoft, но для наших целей и в нашем контексте мы определяем "ActiveX Library" как DLL сервер автоматизации/COM с именем MyServer. Затем создаем объект автоматизации (File | New | ActiveX | Automation Object) с именем MyObject.
unit MyObject;
interface
uses
ComObj, ActiveX, MyServer_TLB;
type
TMyObject = class(TAutoObject, IMyObject)
protected
end;
implementation
uses ComServ;
initialization
TAutoObjectFactory.Create(ComServer, TMyObject, Class_MyObject,
ciMultiInstance, tmApartment);
end.
В вышеприведенном примере мы видим реализацию на Delphi объекта автоматизации с именем MyObject, реализованного с помощью класса TMyObject. MyObject наследуется от класса TAutoObject (из ComObj.pas), который содержит базовые функции, общие для всех объектов автоматизации. TMyObject также поддерживает интерфейс IMyObject в качестве интерфейса автоматизации по умолчанию (или IDispatch, о нем немного позже). Не вдаваясь в детали, первичной целью IMyObject является "публикация" функциональности (методы, свойства, события и т.д.) объекта MyObject для внешнего мира таким образом, что клиенты автоматизации могут пользоваться объектом MyObject. Полное обсуждение основ интерфейсов в COM приводится в книге: David Chappell. Understanding ActiveX and OLE; ISBN 1-57231-216-5.
Мы узнали ранее, что автоматизация позволяет любому клиенту использовать объекты автоматизации не зависимо от того, на каком языке написан клиент. Принимая это во внимание, можно ожидать, что клиент, написанный на Visual Basic, создает экземпляр объекта MyObject следующим образом:
Dim oMyObject as object Set oMyObject = TMyObject.Create
Вышеприведенная строка является фикцией, так как TMyObject это внутренний класс Delphi и вызов TMyObject.Create является специфичной для Delphi формой записи, неопределенной в Visual Basic. Точно также, обобщая, Вы не можете вызывать TMyObject.Create ни из C++, ни из Java только потому, что такой синтаксис неприменим в этих языках. Как же тогда программа на VB, C++ или Java или какой-либо другой клиент автоматизации могут создать экземпляр объекта MyObject?
COM имеет концепцию объекта-"фабрики", чья первичная цель в жизни - это создание/соединение с обьектами автоматизации (COM), отсюда и термин "объект-фабрика". Механизм использования фабрик следующий:
На стороне сервера (Ваш сервер автоматизации):
На стороне клиента (Ваш клиент автоматизации):
Заметьте: Шаги 1 и 2 на стороне сервера не являются необходимыми для серверов DLL. При разработке DLL серверов, COM предполагает, что Вы экспортируете функцию (DLLGetClassObject, но о ней немного позже), которую COM вызывает всякий раз, когда клиент затребует фабрику у Вашего сервера. В этом случае DLLGetClassObject выполняет все функции шагов 1 и 2. Также нет необходимости выполнять шаги 1, 2 и 3 на стороне клиента каждый раз, когда клиент желает создать объект COM. Вместо этого COM осуществляет вызов API, осуществляющий все шаги за один вызов функции.
Обращаясь вновь к нашему объекту MyObject, следует заметить, что Delphi вставил строку TAutoObjectFactory.Create в конце модуля. TAutoObjectFactory является реализацией фабрики в Delphi (также называется "фабрикой класса (class factory)" в COM), используемой для создания экземпляров объектов автоматизации. Заметьте, что одна фабрика обычно используется для обслуживания потребностей одного типа объектов, поэтому для каждого объекта автоматизации (унаследованного от TAutoObject), который Вы создаете в Delphi, обычно Вы видите (или Вам требуется) включить соответствующую строку TAutoObjectFactory.Create для этого объекта. Также важно отметить, что обычно имеется только один экземпляр фабрики (на экземпляр процесса сервера), который используется для создания любого числа объектов автоматизации/COM, с которыми ассоциирована эта фабрика.
TAutoObjectFactory.Create принимает несколько параметров, вот что они означают:
Фабрика позволяет создавать свой ассоциированный объект автоматизации/COM поддержкой интерфейса IClassFactory. IClassFactory определяется следующим образом:
IClassFactory = interface(IUnknown)
['{00000001-0000-0000-C000-000000000046}']
function CreateInstance(const unkOuter: IUnknown; const iid: TIID;
out obj): HResult; stdcall;
function LockServer(fLock: BOOL): HResult; stdcall;
end;
Не вдаваясь во многие детали IClassFactory.CreateInstance предоставляет механизм, посредством которого клиент может просить фабрику создать экземпляр объекта COM. Мы изучим более детально как в действительности клиент использует фабрику для создания экземпляров объектов COM несколько позже в этой статье.
Итак, мы увидели, как мы можем легко создавать объекты автоматизации в Delphi, используя TAutoObject и как мы можем сделать объекты автоматизации доступными для клиентов, используя TAutoObjectFactory. Однако мы не увидели, как мы можем получить какие-либо полезные функции от наших объектов автоматизации, чтобы клиенты могли ими воспользоваться. Это происходит посредством интерфейса IMyObject.
Нормой для объектов автоматизации является поддержка интерфейса автоматизации по умолчанию, раскрывающего все его функции. Этот интерфейс обычно наследуется от IDispatch, который обязательно требуется COM для осуществления "способности к автоматизации (automation capability)". Используя этот интерфейс, Вы можете получить доступ к методам и свойствам, позволяющим клиентам Вашего объекта автоматизации получить доступ ко всей функциональности. Например, возвращаясь к MyObject, мы можем добавить метод (используя редактор библиотеки типов) под названием DoSomething к IMyObject и затем реализовать TMyObject.DoSomething следующим образом:
type TMyObject = class(TAutoObject, IMyObject) protected procedure DoSomething; safecall; end; implementation procedure TMyObject.DoSomething; begin ShowMessage ('MyObject just did something!'); end;
При таком методе псевдокод для клиентского приложения, которое хочет использовать функцию MyObject.DoSomething, мог бы выглядеть следующим образом:
var v : variant; v := Create an instance of MyObject; v.DoSomething;
Из этого очень простого примера теперь очень просто увидеть возможности того, что может быть реализовано в объекте автоматизации. Вы можете создать множество объектов, начиная со складывающих два числа до обрабатывающих данные реального времени для сотен клиентов Ваших Web-серверов. Прекрасное качество Ваших объектов заключается в том, что они независимы от языка (reusable across languages) и они могут существовать где угодно в Вашей сети (can exist anywhere on your network).
Внутри клиента автоматизации
Клиент автоматизации - это приложение, способное извлекать пользу из сервисов и функциональности, заключенных в сервере автоматизации. Клиент автоматизации может быть EXE-приложением, модулем DLL и даже script-файлом, способным выполняться с использованием виртуальной машины (интерпретатора скрипта), такой как, например, Active Server Page (ASP) от Microsoft.
Наиболее общей операцией, производимой клиентом автоматизации, является создание экземпляра объекта автоматизации и выуживание функциональности, заключенной в сервере автоматизации. Как я уже говорил раньше, нормальный путь использования клиентом объекта автоматизации включает:
Вам может показаться, что весь процесс выглядит перевернутым вверх ногами, и это только для создания экземпляра объекта автоматизации. На практике этот процесс очень эффективен, потому как он предоставляет доступ с любых языков программирования для создания экземпляров объектов автоматизации/COM любых типов унифицированным и последовательным способом. Для углубленного изучения обратитесь к David Chappell - Understanding ActiveX and OLE и Dale Rogerson - Inside COM; ISBN 1-572-31349-8 (имется перевод на русский язык: Дейл Роджерсон - Основы COM; ISBN 5-7502-0033-7).
Для того, чтобы предоставить клиентам доступ к фабрике, COM имеет функцию CoGetClassObject:
function CoGetClassObject (const clsid: TCLSID; dwClsContext: Longint; pvReserved: Pointer; const iid: TIID; out pv): HResult; stdcall;
CoGetClassObject принимает несколько параметров, ниже приведено их назначение:
Получив однажды успешно интерфейс IClassFactory фабрики, клиент может вызвать затем IClassFactory.CreateInstance для создания экземпляра желаемого объекта COM.
function IClassFactory.CreateInstance(const unkOuter: IUnknown; const iid: TIID; out obj): HResult; stdcall;
IClassFactory.CreateInstance принимает несколько параметров и ниже приведено их описание:
В соответствии с вышесказанным следующий пример демонстрирует, как клиент на Delphi может создать экземпляр объекта MyObject и вызвать его метод DoSomething.
uses
Windows,
MyServer_TLB,
ActiveX,
ComObj;
procedure TForm1.Button1Click(Sender: TObject);
var
hr : HResult;
pMyObjectFactory : IClassFactory;
pMyObject : IMyObject;
begin
// получаем фабрику объекта MyObject (интерфейс IClassFactory)
hr := CoGetClassObject (Class_MyObject, CLSCTX_SERVER, NIL,
IClassFactory, pMyObjectFactory);
OleCheck (hr);
// запрашиваем фабрику объекта MyObject для создания экземпляра MyObject
hr := pMyObjectFactory.CreateInstance (NIL, IMyObject, pMyObject);
OleCheck (hr);
// теперь у нас есть экземпляр объекта MyObject, запускаем метод DoSomething
// этого экземпляра!
pMyObject.DoSomething;
end;
Заметьте, что в вышеприведенном примере я использую процедуру OleCheck (ComObj.pas) для проверки состояния после вызова каждой, связанной с COM, операции. Почти все COM API или методы интерфейса COM возвращают код своего завершения HResult (longint). Цель вызовов OleCheck - гарантировать, что в случае возврата ошибочного кода завершения HResult, немедленно будет сгенерирована исключительная ситуация с указанием вида ошибки, а дальнейшее исполнение (которое может привести к непредсказуемым результатам) будет прервано. После этого объяснения я настоятельно рекомендую использовать процедуру OleCheck в Ваших программах, чтобы не заниматься поиском потенциальных ошибок, которые могут быть выявлены простыми вызовами OleCheck.
В дальнейшемВы поймете, что CoGetClassObject необходимо вызывать после IClassFactory.CreateInstance каждый раз, когда нам понадобится получить доступ к объекту COM. COM предлагает также и другой вариант - вызов функции API CoCreateInstance, который упрощает 2 вызова, заменяя их одним, что более удобно в использовании в случае, когда необходимо получить доступ ко множеству объектов различных типов в одной сессии:
function CoCreateInstance(const clsid: TCLSID; unkOuter: IUnknown; dwClsContext: Longint; const iid: TIID; out pv): HResult; stdcall;
CoCreateInstance использует несколько параметров и ниже приведено их описание:
Возвращаясь к нашему примеру, CoCreateInstance может быть использован следующим образом:
uses
Windows,
MyServer_TLB,
ActiveX,
ComObj;
procedure TForm1.Button1Click(Sender: TObject);
var
hr : HResult;
pMyObject : IMyObject;
begin
// предыдущие шаги больше не нужны из-за использования CoCreateInstance
hr := CoCreateInstance (Class_MyObject, NIL, CLSCTX_SERVER,
IMyObject, pMyObject);
OleCheck (hr);
// теперь у нас есть экземпляр объекта MyObject, запускаем метод DoSomething
// этого экземпляра!
pMyObject.DoSomething;
end;
Но если Вы хотите использовать что-то такое, что проще запомнить и имеет встроенную поддержку проверки кодов завершения вместо OleCheck, Вы можете использовать функцию CreateComObject (ComObj.pas), поставляемую в составе Delphi:
uses MyServer_TLB, ComObj; procedure TForm1.Button1Click(Sender: TObject); var pMyObject : IMyObject; begin // проще выглядящая CoCreateInstance с использванием функции Delphi // CreateComObject pMyObject := CreateComObject (Class_MyObject) as IMyObject; // теперь у нас есть экземпляр объекта MyObject, запускаем метод DoSomething // этого экземпляра! pMyObject.DoSomething; end;
Вы еще не любите людей из Inpise, всегда делающих жизнь проще для тех из нас, кто является программистом на Delphi?
Заметьте, что в вышеприведенном примере производится приведение типов возвращаемого функцией CreateComObject значения к IMyObject с использованием оператора as только потому, что CreateComObject внутри себя вызывает CoCreateInstance, запрашивая указатель на интерфейс IUnknown (как она может знать, что мы хотим получить IMyObject, если мы даже не передаем IMyObject в CreateComObject?). Если Вы не знакомы с оператором as в данном контексте, то он используется для получения желаемого интерфейса (IMyObject в нашем случае) через посредство другого интерфейса (IUnknown в нашем случае) путем неявного вызова IUnknown.QueryInterface и, в то же время, производя проверку правильности результата выполнения IUnknown.QueryInterface.
Я хотел бы еще продемонстрировать Вам следующий фрагмент текста из файла MyServer_TLB.pas, который автоматически генерируется редактором библиотеки типов Delphi:
type
CoMyObject = class
class function Create: IMyObject;
class function CreateRemote(const MachineName: string): IMyObject;
end;
class function CoMyObject.Create: IMyObject;
begin
Result := CreateComObject(CLASS_MyObject) as IMyObject;
end;
Теперь Вы хорошо знаете, что это означает: просто использование класса CoMyObject для создания экземпляров MyObject, т.е.
uses MyServer_TLB; procedure TForm1.Button1Click(Sender: TObject); var pMyObject : IMyObject; begin // простейший метод создания MyObject, используя // объявление компонентного класса CoMyObject pMyObject := CoMyObject.Create; pMyObject.DoSomething; end;
Если Вы удивляетесь, что за суета с происходит с именами функций "Co-это, Co-то" и именами классов, то это все из-за того, что Co является сокращением для CoClass. На жаргоне COM CoClass является маскирующим термином для создаваемого клиентом объекта COM.
Заметьте, что все примеры, которые мы видели в данной статье, осуществляют прямые манипуляции с интерфейсом IMyObject вместо того, чтобы вызывать IMyObject.DoSomething. В терминологии COM такой процесс "прямого связывания (directly binding)" с IMyObject обычно трактуется как раннее связывание. Другими словами раннее связывание - это процесс запуска сервисов объектов COM с использованием определенного, установленного во время компиляции вызова, такого, например, как вызов методов класса, созданного внутри Delphi. Не беспокойтесь, если Вы удивлены этим определением: мы узнаем несколько больше о раннем связывании позже в этой статье.
Интерфейс IDispatch
До сих пор я рассматривал вопрос, что из себя представляет IDispatch и где он используется при разработке объектов автоматизации и серверов. Прежде, чем погрузиться в детали IDispatch, давайте попытаемся понять, зачем он нужен.
Основная идея IDispatch заключается в том, что где-то должен быть механизм, позволяющий клиентам автоматизации различных типов доступаться к методам объектов автоматизации простым, совместимым и прямо ведущим к цели способом. Множество клиентов автоматизации, в частности, основанные на скриптах, таких как VBScript или JavaScript, не поддерживают манипуляции с массивами указателей на интерфейсы (использование раннего связывания) при доступе к объектам автоматизации. Давайте рассмотрим пример.
Следующий пример показывает как клиент использует скрипт Visual Basic for Applications (VBA) для создания экземпляра MyObject и вызова MyObject.DoSomething:
Dim vMyObject as Object
Set vMyObject = CreateObject ("MyServer.MyObject")
vMyObject.DoSomething
Заметьте, что этот скрипт будет исполняться с помощью интерпретатора скрипта (или на виртуальной машине) и, следовательно, интерпретатор, очевидно, будет нуждаться в получении доступа к интерфейсу IMyObject объекта MyObject для исполнения vMyObject.DoSomething (DoSomething является методом IMyObject). Далее, этот же самый интерпретатор скрипта также должен иметь возможность выполнять все (и любые) запросы создания объектов автоматизации и их методы, что требует от интерпретатора знания всех возможных в мире интерфейсов (IThisObject - IЭтотОбъект, IThatObject -IТотОбъект, IWhatnotObject - IЧтоНеОбъект и т.д.), которые мы только могли бы придумать. Очевидно, что такой интерпретатор спроектировать невозможно, и, даже если бы это было возможно, его невозможно было бы использовать практически. Для решения этой проблемы введена концепция "позднего связывания".
Говоря простыми словами, позднее связывание является особенностью, позволяющей клиентам COM доступаться к методам объектов COM без предварительного знания (или даже необходимости знать) о действительном интерфейсе COM, определяющем методы. Другими словами, позднее связывание позволяет клиентам, работающим на скриптах, вызывать vMyObject.DoSomething без необходимости использования интерпретатором интерфейса IMyObject. IDispatch и есть тот механизм, который в основном обеспечивает позднее связывание.
IDispatch определяется следующим образом:
IDispatch = interface(IUnknown)
['{00020400-0000-0000-C000-000000000046}']
function GetTypeInfoCount(out Count: Integer): HResult; stdcall;
function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult;
stdcall;
function GetIDsOfNames(const IID: TGUID; Names: Pointer;
NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;
function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;
Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult;
stdcall;
end;
Наиболее важными методами интерфейса IDispatch, используемыми для позднего связывания, являются IDispatch.GetIDsOfNames и IDispatch.Invoke. Идея того, как реализуется позднее связывание посредством IDispatch, состоит в следующем:
Учитывая приведенные требования, теперь можно очень легко понять, как интерпретатор может пользоваться только IDispatch для работы с любыми типами объектов автоматизации. Например, следующие шаги показывают как интерпретатор исполняет наш вызов vMyObject.DoSomething:
В мире автоматизации четыре ступени, которые я описал, определяют термин "позднее связывание". Если Вы следили внимательно, метод позднего связывания включает в себя:
Это противоположно тому, что происходит при "раннем связывании", которое включает в себя:
Следующие наблюдения описывают различия между поздним и ранним связыванием:
Следующий фрагмент показывает вариант позднего связывания, реализованный в Delphi, который создает объект MyObject и вызывает DoSomething:
uses
MyServer_TLB,
ActiveX,
ComObj;
procedure TForm1.Button2Click(Sender: TObject);
const
// представляет из себя пустой массив параметров
dpNoArgs : TDispParams = (
rgvarg : NIL; rgdispidNamedArgs: NIL; cArgs : 0; cNamedArgs: 0
);
var
pMyObject : IDispatch;
sMethodName : widestring;
iDispId : longint;
begin
// позднее связывание с MyObject с использованием IDispatch
pMyObject := CreateComObject (Class_MyObject) as IDispatch;
// вызов GetIDsOfNames для получения dispid метода DoSomething в iDispId
sMethodName := 'DoSomething';
OleCheck (pMyObject.GetIDsOfNames (GUID_NULL, @sMethodName, 1,
LOCALE_SYSTEM_DEFAULT, @iDispId));
// вызов Invoke с использованием iDispId метода DoSomething
OleCheck (pMyObject.Invoke (iDispId, GUID_NULL, LOCALE_USER_DEFAULT,
DISPATCH_METHOD, dpNoArgs, NIL, NIL, NIL));
end;
Я не хочу вдаваться в детали передаваемых в IDispatch.GetIDsOfNames и IDispatch.Invoke параметров (они обсуждаются в последнем разделе), но вышеприведенный пример ясно показывает те шаги, которые необходимо выполнить для осуществления вызова с использованием позднего связывания. Очевидно, что для осуществления множества вызовов методов при позднем связывании, приходится проделывать множество вызовов IDispatch.GetIDsOfNames и IDispatch.Invoke. В этой связи люди из Inprise упростили это, доверив компилятору Delphi сделать все необходимые вызовы IDispatch за Вас. Вот пример:
uses MyServer_TLB, ActiveX, ComObj; procedure TForm1.Button2Click(Sender: TObject); var vMyObject : variant; begin // упрощение вызовов позднего связывания, доверяя обслуживание // вызовов GetIDsOfNames и Invoke компилятору Delphi vMyObject := CreateComObject (Class_MyObject) as IDispatch; vMyObject.DoSomething; end;
Здесь нет никакого волшебства: просто компилятор генерирует некий код "за сценой", который производит все неоходимые вызовы IDispatch при выполнении vMyObject.DoSomething. Если бы компилятор Delphi был бы скрипт-интерпретирующим, то он производил бы в точности те же действия, что и интерпретатор Visual Basic for Applications, пример программы на котором мы видели ранее.
IDispatch имеет еще 2 метода, которые мы еще не обсуждали. Не вдаваясь сейчас в какие-либо детали, отметим, что IDispatch.GetTypeInfoCount и IDispatch.GetTypeInfo в основном работают с информацией о типах, получаемой от объектов автоматизации. Информация о типе - это только технический термин для "описания" данного интерфейса автоматизации в том смысле, что информация о типе позволяет клиентам запрашивать информацию об интерфейсе автоматизации, такую как: сколько медотов он имеет, какие имена имеют методы, какие параметры имеет каждый метод и т.д. Информация о типе управляется интерфейсом ITypeInfo (другой интерфейс COM). Мы увидим и другие детали этого интерфейса позже в данной статье.
Если Вы удивлены, как мы далеко зашли даже без упоминания функции Delphi CreateOleObject, то это потому, что я хотел показать, что для иллюстрации работы объекта автоматизации нет необходимости в использовании CreateOleObject. Я прочитал множество текстов и большинство из них старались показать, что CreateOleObject является единственным путем, с помощью которого можно показать работу объектов автоматизации в клиентах, написанных на Delphi. Хорошо, дайте теперь мне сказать несколько слов о CreateOleObject:
Идея, кроющаяся в использовании ProgId, заключается в способе идентификации компонентных классов (CoClasses) (об уникальных идентификаторах фабрик классов упоминалось выше) с использованием дружественного пользователю имени в виде строки вместо использования ClassId (который на самом деле является GUID). Эта строка обычно имеет форму <Server Name.Object Name> и определяется точкой входа в библиотеке типов. ProgId необходимы только для тех клиентских окружений (client environments), которые не позволяют работать с неприличными GUID. Другими словами ProgId - это просто удобная форма предоставления возможности работы с COM объектами для определенных реализаций клиентов.
Следующий фрагмент демонстрирует как CreateOleObject используется совместно с вызовом позднего связывания:
uses
ActiveX,
ComObj;
procedure TForm1.Button2Click(Sender: TObject);
var
vMyObject : variant;
begin
// CreateOleObject и вызов позднего связывания
vMyObject := CreateOleObject ('MyServer.MyObject');
vMyObject.DoSomething;
end;
Заметьте, что "MyServer.MyObject" происходит от имени нашего сервера "MyServer" и имени объекта MyObject, как это определено в библиотеке типов.
Delphi позволяет Вам работать и с неприличными GUIDами (и, следовательно, с ClassId), что означает, что если у Вас имеется доступ к ClassId, то нет необходимости в использовании CreateOleObject. Delphi предоставляет в Ваше распоряжение утилиту для импорта библиотеки типов (Project | Import Type Library или использование утилиты TLibImp.exe) для генерации файла "интерфейса библиотеки типов" (xxx_TLB.pas) почти для любых серверов COM. Этот файл предоставляет доступ ко всем GUIDам, компонентным классам (CoClasses), определениям интерфейсов (Interface definitions) и т.д., которые имеются в сервере. Вы можете легко "связаться" с этим файлом и использовать определения компонентных классов (CoClass) для создания экземпляров объектов COM.