Предыдущая Оглавление Следующая

ITypeLib, ITypeInfo, библиотеки типов и информация о методах сервера. Язык IDL.

В данном разделе рассказано о решении проблемы, состоящей в том, каким образом СОМ-сервер может информировать среду разработки об интерфейсах, их GUID, списке поддерживаемых методов и параметрах этих методов. В самом деле, СОМ-клиент всегда создается после того, как был создан СОМ-сервер. Было бы разумно получить от СОМ-сервера список его методов и список формальных параметров этих методов. Это позволило бы осуществить синтаксический контроль на этапе разработки клиента. Кроме того, названия методов и формальных параметров несут в себе смысловую нагрузку: разработчик легче их воспринимает, и часто назначение методов и параметров очевидно. Также список методов и параметров определенных пользователем интерфейсов необходим для доступа к виртуальной таблице методов интерфейса - реализации так называемого раннего связывания (early binding).

Данная проблема решается при помощи библиотеки типов. При создании СОМ-сервера может быть реализована библиотека типов (а может быть и не реализована - в этом случае доступ к методам СОМ-сервера осуществляется через интерфейс IDispatch). Библиотека типов представляет собой список фабрик класса, поддерживаемых данным СОМ-сервером. Для каждой фабрики класса можно получить список интерфейсов. Для каждого интерфейса приводится список поддерживаемых методов, для каждого из которых можно затребовать список формальных параметров. Порядок следования методов в списке соответствует их порядку в виртуальной таблице методов, поэтому на этапе компиляции клиента можно связать вызов метода сервера с соответствующим столбцом виртуальной таблицы.

Все эти списки сохраняются либо непосредственно в файлах СОМ-сервера (*.ехе; *.dll; *.ocx), либо в отдельных файлах (*.tlb; *.olb). Для последних файлов предусмотрены специальные двоичные форматы списков. Соответственно языки программирования, поддерживающие импорт библиотек типов, должны знать форматы этих файлов, уметь читать данные из них и представлять эти данные разработчику в естественном, специфическом для данного языка программирования виде. Например, если библиотека типов импортирована в средство разработки, использующее компилятор C++, то ее реализация должна быть представлена с использованием синтаксиса C++; если импортирована в Delphi - то с использованием синтаксиса Pascal. При этом разработчик должен отдавать себе отчет, что реально библиотека типов хранится совсем в других форматах.

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

Для создания и редактирования библиотек типов в C++Builder имеется редактор, совмещенный с редактором интерфейсов (определение фабрик класса, интерфейсов и их методов).

Многие средства разработки, используемые для создания СОМ-серверов, содержат в комплекте поставки утилиты для автоматической генерации библиотек типов или клиентского и серверного кода на основании описаний интерфейсов сервера. Во многих случаях эти описания создаются на языке IDL (Interface Definition Language).

Зачем нужен IDL?

IDL используется для спецификации интерфейсов. Фактически это стандарт, позволяющий описывать вызываемые методы сервера и их параметры не вдаваясь в детали и правила реализации серверов и клиентов на том или ином языке программирования. Используя IDL, можно описать интерфейсы сервера, а затем создать его реализацию на любом языке программирования с помощью широкого спектра средств разработки, равно как и реализацию клиента. В определенном смысле IDL - это стандарт для описания взаимодействия между компонентами распределенной системы, не зависящий от деталей реализации и языков программирования (а в общем случае и платформ, так как IDL используется не только в СОМ).

IDL COM заимствован из IDL DCE (Distributed Computing Environment) - спецификации межплатформенного взаимодействия сервисов, разработанной консорциумом Open Systems Foundation. Отметим, что в настоящее время существует несколько диалектов IDL- (для СОМ, для CORBA, для DCE и др.). Тем не менее различия между ними невелики.

Язык IDL немного похож на ту часть C++, которая содержит описания клaccoв (то, что обычно помещается в h-файлы). В качестве примера примем описание на IDL интерфейсов гипотетического СОМ-сервера, содержащего объект MyComObj, интерфейс которого IMyComObj (наследник IUnknown) экспонирует два метода: MyMethodl, получающий в качестве входных параметров два целых числа и возвращающий действительное число, и MyMethod2, не возвращающий данных и получающий в качестве входного параметра переменную типа Variant:

[
  uuid(845256DO-8E96-11D2-B126-000000000000),
  version(1.O),
  helpstring("Project1 Library")
]
library Project1
{
  importlib("STDOLE2.TLB");
  importlib("STDVCL60.DLL");
  [
    uuid(845256D1 -8E96-11D2-B126-000000000000),
    version(1.O),
    helpstring("Interface for myComObj Object")
  ]
  interface ImyComObj: IUnknown
  {
    [id(OxOOOOOOOI)]
    double _stdcall MyMethod1([in] long Paraml, [in] long Param2 );
    [id(0x00000002)]
    void _stdcall MyMehod2([in] VARIANT ParamЗ);
  };
  [
    uuid(845256D3-8E96-11D2-B126-000000000000),
    version(1.O),
    helpstring("myComObj Object")
  ]
  coclass myComObj
  {
    [default] interface ImyComObj;
  };
}

Многие средства разработки, поддерживающие создание СОМ-серверов, имеют в своем составе утилиты для генерации серверного и клиентского кода на основании описаний на IDL. В частности, в состав Microsoft Visual C++ включен компилятор MIDL, генерирующий код для клиентских и серверных DLL, ответственных за взаимодействие клиента и сервера, на основании созданных разработчиками описаний на IDL.

Отметим, однако, что при создании СОМ-серверов с помощью экспертов C++Builder библиотеки типов и соответствующий код (или его "заготовки") генерируются автоматически и создавать вручную описания с помощью IDL необходимости нет. При этом всегда можно сгенерировать описание на IDL на основании библиотеки типов сервера, созданной с помощью соответствующего редактора.

IDispatch и вызов методов "по имени"

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

В СОМ-технологии предусмотрена возможность доступа к методам интерфейса при отсутствии информации о порядке реализации методов в виртуальной таблице. При реализации интерфейсов среда разработки может сохранить текстовые названия методов и запомнить их в файле, где находится скомпилированный код СОМ-сервера. Это означает, что в СОМ-сервере был реализован интерфейс IDispatch. Он имеет два основных метода: GetIDOfNames и Invoke. Клиент обычным путем получает ссылку на интерфейс IDispatch сервера и может вызвать его метод GetIDOfNames. В качестве параметра указывается текстовое название метода, которое интересует клиента. Если IDispatch находит на сервере метод с таким названием, то он возвращает его идентификатор - DispID. Далее клиентское приложение использует этот идентификатор и специальным образом упакованные переменные в качестве параметров другого метода IDispatch - Invoke. Сервер распаковывает этот список параметров и вызывает метод с данным DispID с полученным от клиента списком параметров.

Поскольку связь между вызываемыми методами устанавливается при выполнении приложения, то такой способ вызова методов интерфейсов называют поздним связыванием (late building). Соответственно для вызова методов таким образом требуется провести большее число операций, чем при прямом обращении к виртуальной таблице, и это может сказаться на скорости их выполнения. При отсутствии библиотеки типов на этапе разработки сервера невозможно проверить правильность написания имен методов и списков параметров методов. Это может обнаружиться только на этапе выполнения клиентского приложения, когда произойдет исключение. Поэтому клиентское приложение, использующее сервер с помощью интерфейса IDispatch, обязательно следует тестировать на предмет корректного выполнения всех команд.

При разработке и тестировании СОМ-сервера также лучше создать клиентское приложение, реализующее вызовы методов сервера через IDispatch. Это лучше сделать потому, что таблица виртуальных методов какого-либо интерфейса на сервере может модифицироваться при добавлении и/или удалении методов. При этом порядок их следования в таблице изменяется и необходимо заново импортировать библиотеку типов, что разработчик часто сделать забывает. В этом случае при запуске клиентского приложения, как правило, вскоре происходит крах стека, и при работе в среде Windows 95 это зачастую приводит к краху операционной системы.

Кроме того, при наличии библиотеки типов, при реализации нотификационных сообщений в элементах управления ActiveX используется вызов нотификаций через интерфейс IDispatch, и только через него! Элемент управления ActiveX определяет интерфейс для поддержки нотификационных сообщений, но сам его не создает. Он обязан быть реализован на клиенте, а элемент управления ActiveX обязан получить ссылку на него и вызывать методы этого интерфейса в ответ на события, происходящие с ним. Поскольку СОМ-сервер обязан быть скомпилирован раньше клиента, нет никакой возможности на этапе компиляции сервера получить доступ к таблице виртуальных методов клиента и связать нотификационные сообщения с методами, определенными в клиенте.

IMarshall, маршалинг и взаимодействие клиента с внутренними, локальными и удаленными серверами

Маршалинг был реализован в СОМ для решения проблемы экспорта объектов через адресное пространство процесса. Дальнейшее развитие СОМ-технологии, получившее название DCOM (Distributed COM), позволило осуществлять взаимодействие объектов, выполняющихся на различных компьютерах.

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

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

Эта структура позволяет обеспечить маршалинг (marshalling) - пересылку указателя между процессами (и в общем случае между компьютерами, иногда называется "маршрутизация").

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

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

Как было рассказано ранее, местоположение сервера определяется на основании записи в реестре. Затем сервер загружается в оперативную память (если он еще не загружен или если требуется новый экземпляр сервера), создает нужный объект и возвращает клиенту указатель на интерфейс.

Если СОМ-сервер является внутренним, то есть выполненным в виде библиотеки DLL, она загружается в адресное пространство клиента с помощью функции Win32 API LoadLibrary. В этом случае значение указателя на интерфейс непосредственно доступно клиенту.

Если сервер локальный, СОМ использует функцию CreateProcess, загружая исполняемый файл и инициализируя СОМ в адресном пространстве последнего. В этом случае обычно нет возможности передать клиенту значение указателя на интерфейс, так как это указатель на объект в другом адресном пространстве. В этом случае в адресных пространствах клиента и сервера создаются два объекта: stub (заглушка) - представитель клиента в адресном пространстве сервера, имеющий дело с реальным указателем на интерфейс, и proxy (заместитель) - представитель сервера в адресном пространстве клиента. Эти два объекта соединяются между собой с целью передачи клиентскому процессу указателя на интерфейс. В этом случае используется маршалинг, создающий так называемый marshalling packet - пакет данных, содержащий необходимую информацию для соединения с процессом, в котором создан объект. Этот пакет создается с помощью функции COM API CoMarshallInterface, затем он передается процессу клиента любым доступным способом, где другая функция CoUnMarshallInterface превращает этот пакет в указатель на интерфейс. Стандартный маршалинг осуществляется с помощью интерфейса IMarshall . Методы CoMarshallInterface и CoUnMarshallInterface вызываются соответственно в реализациях методов интерфейса IMarshall MarshallInterface и UnMarshallInterface.

Сходная технология используется при осуществлении вызовов удаленных процедур (RFC - Remote Procedure Calls), откуда она и была заимствована Microsoft.

Естественно, proxy-объект не содержит реализации методов интерфейса. Все аргументы вызываемых методов помещаются в пакет, передаваемый stub-объекту с использованием RPC. Stub-объект распаковывает переданные аргументы, помещает их в стек и обращается к реальному объекту, используя существующий указатель на интерфейс. Результат выполнения метода упаковывается в пакет и посылается proxy-объекту, который распакот вывает его и передает клиенту.

В случае сервера, расположенного на удаленном компьютере, при обращение к серверу СОМ соединяется со специальным резидентным процессом удаленного компьютера, контролирующим удаленный запуск сервисов на нем (наличие такого процесса диктуется обычными соображениями безопасности). Этот процесс, называемый иногда Service Control Manager (SCM), осуществляет запуск сервера на удаленном компьютере и возвращав ет указатель на интерфейс клиентскому компьютеру и клиентскому процессу. В остальном маршалинг осуществляется точно так же, как и в случае локального сервера, за исключением того, что proxy-объект и stub-объект общаясь с помощью того же самого механизма RPC, физически находятся на разных компьютерах.

Технология OLE

Внедрение и связывание объектов - OLE

Прежде, чем рассматривать реализацию в C++Builder технологии внедрениям связывания объектов OLE (произносится "оле" с ударением на последний слог), остановимся на некоторых базовых понятиях. Эта технология появилась как OLE 1.0 в Windows 3.1 и означала, что пользователь мог создавать сложные составные документы, в которых содержались объекты различного происхождения. Внедренные объекты могли редактироваться простым двойным щелчком мыши на соответствующем элементе данных. Например, можно было дважды щелкнуть на электронной таблице Excel, встроенной в документ редактора Word, и в отдельном окне запускался Excel с загруженным рабочим листом, готовым к редактированию. После завершения редактирования Excel позволял сохранить изменения во внедренном в документ Word объекте Excel.

Другой особенностью было связывание объектов. Это позволяло связать электронную таблицу с документом Word (по сути, внутри документа Word хранился просто указатель на электронную таблицу). Если данные в оригинале электронной таблицы обновлялись, то при следующей загрузке документа Word ссыпка обновляла документ и отражала в нем проведенные изменения.

Дальнейшее развитие внедрение и связывание получило в OLE 2.0. Основой этого усовершенствованного подхода явилась компонентная модель объекта (СОМ), Это модель объекта в системном обеспечении, которая предусматривает полную совместимость во взаимодействии между компонентами, написанными разными компаниями и на разных языках. Ключом к успеху является модульность этих компонентов. Они могут покупаться, модернизироваться или заменяться по одиночке или группами, причем это никак не влияет на работу целого. Новая особенность, появившаяся в OLE 2.0, — это автоматизация OLE, которая обеспечивает доступ к объектам приложения и манипуляцию с ними извне. Такие объекты, предоставленные (экспонированные) для внешнего пользования, называются автоматными объектами OLE. Типы объектов, которые могут быть экспонированы, так же разнообразны, как и сами приложения Windows. Текстовый процессор мог бы экспонировать в качестве автоматного объекта документ, абзац или предложение. Электронная таблица могла бы экспонировать таблицу, диаграмму, ячейку или группу ячеек.

Главное отличие автоматных объектов от обычных объектов OLE состоит в том, что автоматные объекты доступны только программно, они создаются и используются при помощи программного кода и, следовательно, в принципе временны. Они не могут быть внедрены или связаны. Они могут существовать только в течение времени выполнения ваших программ и не видны непосредственно конечному пользователю.

Использование OLE-документов в приложениях

Теперь рассмотрим некоторые приемы использования OLE в C++Builder. На странице System библиотеки визуальных компонентов имеется контейнер OLE OleContainer — компонент, обеспечивающий внедрение и связывание. OLE-контейнер инкапсулирует все интерфейсы, необходимые для создания клиента OLE документов. Давайте посмотрим его возможности сначала на очень простом примере. Разместите на форме контейнер OleContainer, компонент главного меню MainMenu, диалоги OpenDialog и SaveDialog. Можно также разместить список изображений ImageList, панель ToolBar и на ней быстрые кнопки, дублирующие основные команды меню. Впрочем, можете этими изысками не заниматься, но меню, как мы увидим ниже, должно быть обязательно.

Панель, если вы ее ввели, выравнивается по верху формы (Align = alTop), a контейнер должен занимать всю оставшуюся площадь формы (Align — alClient).

В MainMenu введите меню "Объект" и в нем разделы "Новый", "Открыть", "Сохранить", "Закрыть". Назовите объекты этих разделов MNew, MOpen, MSave и MClose.

Установите в диалогах в свойстве Filter: «объекты OLE *.ole» и «все файлы *.*». Установите также в диалогах расширение по умолчанию в свойстве DefaultExt равным ole.

Теперь перейдем собственно к программированию. Давайте введем в определении класса формы глобальную переменную FName, в которой будет храниться имя файла объекта OLE:

AnsiString FName;

Процедура, соответствующая разделу меню "Новый", может иметь вид:

void __fastcall TForm1::MNewClick(TObjeot *Sender)
{
  if (OleContainer1->InsertObjectDialog ())
  {
    FName = "";
    OleContainer1->DoVerb (ovShow);
  }
}

Вызываемый этим оператором метод InsertObjectDialog осуществляет обращение к стандартному окну Windows Insert Object (вставка объекта), в котором пользователь может указать тип вставляемого объекта, инициализирует объект OLE и загружает его в контейнер OleContainer1. Вызываемый далее метод DoVerb обеспечивает немедленное открытие программы, связанной со вставленным документом. Вызов DoVerb можно не вводить в приведенный код. Тогда после вставки документа пользователь должен будет сделать двойной щелчок на контейнере, чтобы открыть соответствующую программу.

Работу с окном "Вставка объекта", которую инициирует приведенный код, мы рассмотрим несколько позднее, а пока продолжим создание приложения. Процедура, соответствующая разделу меню "Закрыть", может иметь вид:

void __fastcall TForm1::MCloseClick(TObject *Sender)
{
  OleContainer1->DestroyObject ();
}

Эта процедура разрушает объект в контейнере OLE.

Процедура, соответствующая разделу меню "Сохранить", может иметь вид:

void __fastcall TForm1::MSaveClick(TObject *Sender)
{
  if (FName == "")
    if (SaveDialog1->Execute ())
      FName = SaveDialog1->FileName;
    else return;
  OleContainer1->SaveToFile(ChangeFileExt (FName, ".ole"));
}

Если имя файла не задано, то вызывается диалог Сохранить как (SaveDialog1), с помощью которого пользователь задает имя файла. Затем методом SaveToFile объект сохраняется в файле. При этом во избежание ошибок расширение файла принудительно заменяется на .ole с помощью функции ChangeFileExt, изменяющей расширение файла.

Процедура, соответствующая разделу меню "Открыть", может иметь вид:

void __fastcall TForm1::MOpenClick(TObject "Sender)
{
  if (OpenDialog1->Execute ())
  {
    OleContainer1->LoadFromFile(OpenDialog1->FileName);
    FName = OpenDialog1->FileName; 
    OleContainer1->DoVerb(ovShow);
  }
}

Она обычным образом вызывает диалоговое окно, в котором пользователь выбирает открываемый файл. Затем объект, соответствующий этому файлу, загружается в контейнер методом LoadFromFile. Имя файла запоминается в переменной FName. Последний оператор методом "DoVerb" открывает программу, связанную с загруженным объектом. Этот оператор записывать не обязательно. Но тогда для открытия программы пользователь должен будет сделать двойной щелчок на контейнере OLE.

Теперь посмотрим, как работает наше приложение. Запустите его на выполнение и выполните команду "Новый". Перед вами откроется окно Вставка объекта (Insert Object). В этом окне вам предоставляются две возможности: создание нового объекта OLE (радиокнопка "Создать новый"), или создать объект из имеющегося файла (радиокнопка "Создать из файла").

Рассмотрим сначала первую возможность. В этом случае в окне "Тип объекта" вы должны указать тип вставляемого в контейнер объекта. Это может быть документ Word, лист Excel, объект звукозаписи, точечный рисунок и т.п. Правда, к сожалению, надо отметить, что не любой тип объекта и не в любой версии Windows может быть вставлен. Так что с переносимостью подобных приложений могут иногда возникать проблемы. Если вставка не получится, вам будет выдано соответствующее сообщение.

После того как вы выбрали тип объекта, щелкните на ОК. Дальнейшее зависит от того, включили ли вы в код процедуры MNewClick вызов метода DoVerb. Если не включали, то в первый момент в контейнере OLE вашего приложения отобразится вид объекта по умолчанию. Этот вид зависит от типа объекта. Возможно, в первый момент вы вообще ничего не увидите. Надо будет сделать двойной щелчок на контейнере, чтобы вызвать программу, обслуживающую объект этого типа. Если же вы включили в код процедуры MNewClick вызов метода DoVerb, программа, обслуживающая объект, откроется без вашего вмешательства.

В обоих случаях вы увидите, что ваше приложение чудесным образом преобразится. В него встроится соответствующая программа вместе со своими инстру ментальными панелями и меню, например, Excel. Вы можете создать какую-то таблицу, построить соответствующие диаграм мы и т.п.

Обратите внимание на то, что ваша инструментальная панель сохранилась на экране и после загрузки объекта и начала работы с ним. И, главное, заметьте, что не все разделы меню программы Microsoft Excel встроились в ваше приложение. Нет первого меню — Файл, в котором содержатся команды открытия и сохранения файлов. Это не случайно, так как далее будет показано, что сохранение объекта OLE отличается от сохранения файла изображения. Вместо меню Файл, имеющегося в Microsoft Excel, встроилось меню Объект вашего приложения, в котором имеются соответствующие команды открытия и закрытия файла. Именно ради этого мы и делали в своем приложении меню.

Выше была продемонстрирована возможность внедрения объекта OLE в результате диалога вставки объекта. Этот диалог позволяет выбрать любой доступный тип документа. Но если тип нового объекта известен, можно внедрять его программно. Например, если требуется внедрить объект страницы Excel, то в обработчик события формы OnCreate или в обработчик щелчка на какой-то кнопке надо вставить операторы:

OleContainer1->CreateObject ("Excel.Sheet", false);
OleContainer1->DoVerb (ovShow);

Для создания объектов иных типов второй оператор остается неизменным, а в вызове метода CreateObject меняется только первый параметр — класс создаваемого объекта OLE. Например, оператор

OleContainer1->CreateObject("Word.Document",false);

создаст документ Word, а оператор

OleContainer1->CreateObject("PowerPoint.Show",false);

создаст слайд PowerPoint.

Мы рассмотрели пока одну из возможностей создания в вашем приложении объекта OLE. Теперь давайте рассмотрим другие возможности. Выполните опять команду "Новый" вашего приложения и выберите радиокнопку "Создать из файла" (Create from File). В этом случае вы можете создать объект OLE на основе имеющегося файла. При этом диалоговое окно изменит свой вид. В нем вы можете с помощью кнопки "Обзор" выбрать какой-нибудь файл документа Word, Excel, графический и т.п. Обратите внимание на очень важный индикатор "Связь" (Link). Пока не устанавливайте этот индикатор, а просто нажмите ОК. Вы создадите в своем приложении внедренный (но не связанный) объект OLE. В контейнер приложения загрузится содержимое файла и откроется обслуживающая его программа, как это было и раньше.

Теперь остановимся на сохранении объекта. Если вы выполните команду вашего приложения "Сохранить", то с помощью обычного диалога сохранения можете записать объект в нужный вам каталог. Тем самым вы создадите файл, содержащий внедренный объект OLE. В дальнейшем вы можете открыть его командой Открыть вашего приложения. Это абсолютно автономный объект, никак не связанный с исходным файлом, из которого он был создан. Но открыть его можно только как объект OLE. Если вы попробуете открыть, например, файл, созданный из объекта Excel, самой программой Excel, вы потерпите неудачу, так как Excel не поймет формата этого файла.

Теперь попробуйте создать внедренный и связанный документ. Выполните опять команду "Новый" вашего приложения, в диалоговом окне "Вставка объекта", опять выберите радиокнопку "Создать из файла" (Create from File), выберите какой-нибудь файл, но на этот раз установите индкатор "Связь" (Link). После щелчка на ОК в окне вашего приложения снова появится выбранный вами объект документа. Вы опять можете сохранить этот объект командой Сохранить. Но теперь документ в вашем объекте не просто внедрен, а и связан с исходным файлом. Это означает, что все изменения в исходном файле отразятся в вашем объекте и наоборот. Кроме того, изменяется и взаимодействие вашего приложения с документом. Раньше программа, обслуживающая документ, открывалась в контейнере OLE. А теперь она открывается в отдельном, полноценном окне программы с загруженным в него документом. Вы можете что-то изменить в документе, напечатать его, сохранить. Как только он будет сохранен, в тексте документа в вашем приложении отразятся введенные изменения.

Вы создали очень простое приложение, использующее OLE. Его можно усовершенствовать, добавив в меню еще несколько разделов. Один из них — "Открыть файл". В отличие от рассмотренного ранее раздела, открывающего объект OLE, в данном разделе можно создавать новый объект на основе какого-то существующего файла документа. Делается это методом GreatcObjectFromFile:

void CreateObjectFromFile(AnsiString FileName, bool Iconic);

Аргумент FileName определяет имя открываемого файла. Второй аргумент Iconic, значение которого обычно задается равным false, показывает, что объект отображается в том виде, в каком он содержится в исходном файле. Если задать этот аргумент, равным true, то объект будет отображаться в виде пиктограммы.

Таким образом, в вашем приложении обработчик команды "Открыть файл" может иметь вид:

void __fastcall TForm1::MOpenFClick (TObject *Sender)
{
  if (OpenDialog1->Execute ())
  {
    OleContainer1->CreateObjectFromFile (OpenDialog1->FileName, false);
    FName = OpenDialog1->FileMame;
    OleContainer1->Repaint ();
  }
}

Выполнение этой команды эквивалентно описанному ранее созданию внедренного объекта OLE из файла, но исключает необходимость пользователю работать с дополнительными окнами.

Имеется также аналогичный метод GreateLinkToFile, позволяющий создавать внедренный и связанный объект:

void CreateLinkToFile (const AnsiString FileName, bool Iconic);

Можете воспользоваться им для включения в ваше меню и такого раздела.

Еще один раздел, который можно добавить в меню вашего приложения — "Сохранить файл". В отличие от рассмотренного ранее раздела, сохраняющего в файле объект OLE, в данном разделе можно сохранять документ, содержащийся в объекте, в его натуральном виде. Это можно сделать оператором, использующим функцию SaveAsDocument:

OleContainer1->SaveAsDocument(FName);

Функции CreateObjectFromFile, CreateLinkToFile и SaveAsDocument позволяют построить фактически универсальный редактор текстовых, графических, музыкальных и других файлов.

Модифицируем приложение, добавив еще две кнопки с надписями "Свойства" и "Вставка".

Создадим соответствующие обработчики событий:

void __fastcall TForm1::Button2Click(TObject *Sender)
{
  OleContainer1->ObjectPropertiesDialog ();
}
void __fastcall TForm1::Button3Click(TObject *Sender)
{
  OleContainer1->PasteSpecialDialog ();
}

Скомпилируем приложение. Добавим какой-нибудь объект в OleContainer.

Нажав на кнопку "Свойства", получим стандартный диалог с описанием свойств объекта.

Нажав кнопку "Вставить", получим диалог вставки объекта.

Выбрав тип вставки объекта, можем вставить его в OleContainer.

В заключение несколько слов об автоматизации OLE. Рассмотренное выше внедрение и связывание позволяют запустить приложение, обрабатывающее соответствующий документ, как единое целое. Автоматизация OLE позволяет управлять этим приложением (сервером OLE), используя экспонированные им функции и процедуры.

Использование свойств OleContainer

Как определить характеристики OLE-объекта, помещенного в OleContainer, и управлять им?

Имя класса OLE-объекта можно получить с помощью свойства OleClassName. Список доступных команд можно получить с помощью свойства ObjectVerbs. Метод DoVerb позволяет вызвать одну из этих команд, ссылаясь на ее порядковый номер в списке. Целочисленное свойство PrimaryVerb содержит номер команды из этого списка, выполняющейся при активации OLE-объекта. Состояние контейнера можно определить с помощью свойства State.

Для иллюстрации их работы на панель с OleContainer поместим компоненты TComboBox и TEdit.

void __fastcall TForm1::Button1Click(TObject *Sender)
{
  if (OleContainer1->State != osEmpty)
  {
    OleContainer1->DoVerb (ComboBox1->Itemlndex);
  }
}
Предыдущая Оглавление Следующая