Сервера автоматизации Доступ к COM серверам Microsoft Office :00:221
Приложения и объекты MS Office Office – это среда, в которой большинство задач можно решать без какого - либо программирования. Но вся ценность приложений Office для разработчика заключается в том, что все, что можно сделать руками, можно сделать программным путем с использованием средств VBA (Visual Basic for Application). Кроме того, приложения Office поставляют сервера COM, которые предоставляют интерфейс доступа к приложению и его объектам. Благодаря этому, разработчик в среде Delphi имеет возможность, создав контроллер автоматизации, управлять сервером. Приложение MSOffice рассматривается как совокупность объектов со своими методами, свойствами, событиями, которые обеспечивают скелет приложения. Программист Office является не создателем приложения, как, например это делается в Delphi, а он принимает участие в создании системы документов. Таким образом, ДОКУМЕНТ, а не программа являются целью разработки :00:222
Объектная модель приложений MS Office Имея объектную структуру, приложение ms office обладает всеми чертами объектной модели. То есть, в объектной модели office существует и наследование, и встраивание классов. Как всегда, существует некий базовый, прародительский объект, который является " отцом " всех встраиваемых объектов. Такой объект называется application. Схема показывает упрощенную иерархию таких объектов. Всегда существует задающий приложение корневой объект, он всегда называется Application. Каждое приложение Office имеет свой собственный корневой объект – Word.Application, Excel.Application. Outlook приложение само является корневым объектом, несмотря на это в объект Application встраиваются все остальные объекты ( участники ), которые являются свойствами главного объекта. Участником могут быть свои участники и так далее. В документе методов очень много, причем самых разных, но имеются и одинаковые методы в различных приложениях Office. Среди таковых – Run, Quit, Activate; но даже и они в разных приложениях имеют различный набор параметров, а зачастую выполняют не адекватные действия :00:223
Объектная модель приложений MS Office :00:224
Объектная модель документа Word :00:225
Объекты Application Фундаментальным объектом любого приложения является Application. Рассмотрим сценарий взаимодействия через сервера автоматизации : :00:226 1)Создаем новый проект 2)На главную форму выкладываем компоненту с закладки Servers, которая называется WordApplication 3)Устанавливаем свойства компоненты AutoConnect и AutoQuit в True 4)Запускаем приложение на выполнение. приложение
Доступ к объекту Application Казалось бы, ничего не произошло, запустилась форма и отобразилась на экране, на самом деле наше приложение запустило сервер автоматизации Microsoft Word, этот факт можно обнаружить, запустив на выполнение Task Manager и выбрав закладку Processes. Среди прочих процессов мы обнаруживаем WINWORD.EXE. На самом деле была приложением проделана следующая работа : При создании формы, в системном реестре, по идентификатору CLSID был найден сервер Word.Application Запущено на выполнение приложение, находящееся по адресу в реестре (ProgID) Сервер предоставил нашему приложению, которое и является контроллером автоматизации интерфейс, через который мы и получим доступ к объекту Application. Интерфейс Idispatch унаследован от Iunknown, который в свою очередь имеет три метода, один из которых _ADDRef умеет считать количество клиентов, в настоящий момент использующих сервер. Как только от сервера отсоединиться последний клиент, он автоматически будет выгружен из памяти компьютера :00:227
Технология OLE Automation ( автоматизация OLE). Сервер автоматизации представляет собой программу, которая может управляться внешней программой контроллером автоматизации. Сервером в данном случае является Word или Excel, а контроллер разрабатывается программистом. Отличие технолгии OLE Automation в том, что она позволяет использовать возможности СОМ не только языкам - компиляторам, но и интерпретаторам, и обеспечивает связь с вызываемыми методами на стадии выполнения приложения. Такой способ вызова называется поздним связыванием. Методы при таком способе вызова выполняются медленнее, причем заранее нельзя проверить правильность написания объектов и их методов. Преимуществом такого метода является независимость выбора среды разработки от объекта, который нужно программировать. Среда Delphi поддерживает вызовы методов серверов автоматизации. Для этого используются переменные типа Variant, которые содержат ссылки на объекты автоматизации. На этапе выполнения программы серверу автоматизации передается команда в виде строки, предварительно записанной в переменную типа Variant :00:228
var wd: OleVariant; fileName: string; begin try fileName := ExtractFilePath(Application.EXEName) + 'report.DOC'; //Создаем объект интерфейса для доступа к серверу COM wd := CreateOleObject('Word.Application'); //Проверка наличия методов и правильность передачи параметров //будет осуществляться на стадии выполнения приложения wd.application.documents.add; wd.application.activedocument.range.insertAfter(now); wd.application.activedocument.saveas(fileName); //выгружаем сервер из памяти компьютера wd.application.quit(true, 0); Except End; :00:229 Доступ к объекту Application Пример 2
Интерфейс IDispatch Интерфейс IDispatch описывается в модуле System следующим образом: type IDispatch = interface(IUnknown) ['{ C }'] function GetTypeInfoCount(out Count: Integer): Integer; stdcall; function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): Integer; stdcall; function GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount, LocaleID; Integer; DispIDs: Pointer): Integer; stdcall; function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): Integer; end; :00:2210
Центральным элементом технологии OLE Automation является интерфейс IDispatch. Ключевыми методами этого интерфейса являются методы GetIdsOfNames и Invoke, которые позволяют клиенту запросить у сервера, поддерживает ли он метод с указанным именем, а затем, если метод поддерживается – вызвать его. Когда клиенту требуется вызвать метод, он вызывает GetIdsOfNames, передавая ему имя запрошенного метода. Если сервер поддерживает такой метод, он возвращает его идентификатор – целое число, уникальное для каждого метода. После этого клиент упаковывает параметры в массив переменных типа OleVariant и вызывает Invoke, передавая ему массив параметров и идентификатор метода. Таким образом, все, что должен знать клиент – это строковое имя метода. Такой алгоритм позволяет работать с наследниками IDispatch из скриптовых языков. Методы GetTypeInfo и GetTypeInfoCount являются вспомогательными и обеспечивают поддержку библиотеки типов объекта. Реализация методов GetIdsOfNames и Invoke, предоставляемая COM по умолчанию базируется на библиотеке типов объекта :00:2211 Интерфейс IDispatch
Dispinterface Dispinterface Dispinterface – это декларация методов, доступных через интерфейс IDispatch. Объявляется он следующим образом: type IMyDisp = dispinterface ['{EE05DFE D0-9EA9-0020AF3D82DA}'] property Count: Integer dispid 1 procedure Clear dispid 2; end; Самих методов может физически и не существовать (например, они реализуются динамически в Invoke) :00:2212
Пример DispInterface Рассмотрим использование dispinterface на простом примере. Объявим диспинтерфейс объекта InternetExplorer и используем его в своей программе:программе type IIE = dispinterface ['{0002DF C }'] property Visible: WordBool dispid 402; end; procedure TForm1.Button1Click(Sender: TObject); var II: IIE; begin II := CreateOleObject('InternetExplorer.Application') as IIE; II.Visible := TRUE; end; Эта программа успешно компилируется и работает, несмотря на то, что в интерфейсе объявлено только одно из множества имеющихся свойств и методов. Это возможно благодаря тому, что Delphi не вызывает методы диспинтерфейса напрямую и, поэтому, не требует полного описания всех методов в правильном порядке :00:2213
Dual интерфейс Идея двойных интерфейсов очень проста. Сервер реализует одновременно некоторый интерфейс, оформленный по стандартам COM (VTable) и диспинтерфейс, доступный через IDispatch. При этом интерфейс VTable должен быть унаследован от IDispatch и иметь идентичный с диспинтерфейсом набор методов. Такое оформление сервера позволяет клиентам работать с ним наиболее удобным для каждого клиента образом. Клиенты, использующие VTable вызывают методы интерфейса напрямую, а клиенты, использубщие позднее связывание – через методы IDispatch. Большинство OLE- серверов реализуют двойной интерфейс. Технология Automation, ранее известная как OLE Automation, дает совершенно другой способ вызова клиентом методов, экспонируемых сервером, чем тот стандартный для СОМ способ, который мы уже рассмотрели. Automation же использует стандартный СОМ - интерфейс IDispatch для доступа к интерфейсам. Поэтому говорят, что любой объект, поддерживающий IDispatch, реализует Automation. Также говорят о дуальном интерфейсе, имея в виду, что он может быть вызван как с помощью естественного способа (vtable), так и с помощью вычурного способа Automation :00:2214
Базовый класс TOLEServer На закладке Service находится набор компонент для доступа к серверам автоматизации, не все компоненты возвращают ссылку на объект Application, то есть могут быть получены интерфейсы для доступа к вложенным объектам, таким как Документ Word или рабочая книга Excel. Все компоненты унаследованы от класса TOLEServer, который наследует свойства класса Tcomponent. TOLEServer является базовым классом всех COM серверов. Кроме этого этот класс имеет еще несколько свойств и методов для управления связью с COM сервером. Среди таковых уже знакомое нам свойство AutoConnect, которое автоматически запускает COM сервер и производит извлечение из него интерфейса, обеспечивающего связь с контроллером. Еще одно важное свойство класса TOLEServer – ConnectKind – указывающее тип процесса, к которому устанавливается связь. Свойство используется методом Connect, который вызывается автоматически, если свойство AutoConnect истинно :00:2215
Значения свойства ConnectKind ckRunningOrNew. Контроллер производит подключение к уже существующему процессу, или запускает новый процесс, при отсутствии такового. Этот вид взаимодействия между COM сервером и контроллером наиболее часто применяется на практике. Такое значение свойства установлено по умолчанию. ckNewInstance. При соединении с сервером каждый раз создается новый экземпляр ckRunningInstance. Соединение устанавливается с уже запущенным COM сервером. Если таковой отсутствует – будет создан соответствующий объект ошибки, который необходимо обработать ckRemote. Это значение используется совместно со свойством RemoteMachineName, если необходимо подключиться к серверу на удаленной машине ckAttachToInterface. При установке этого значения интерфейс не создается и соответственно нельзя указывать значение True для свойства AutoConnect. Соединение с сервером производится с помощью метода ConnectTo :00:2216
Подключение к существующему интерфейсу При необходимости подключения к проекту таких компонентов, как WordDocument или WordParagraphFormat производится подключение к уже существующему интерфейсу не создавая его заново. Также это может быть необходимо, когда контроллер должен отслеживать события, происходящие в COM сервере :00:2217
procedure TForm1.WordApplication1DocumentChange(Sender: TObject); begin //производим подключение к текущему документу WordDocument1.ConnectTo( WordApplication1.ActiveDocument); //Контроллер добавляет новую строку в текущий документ WordDocument1.Range.InsertAfter(#13+'Переход к документу'+#13+ WordApplication1.ActiveDocument.Get_FullName+' произведен :'+ DateTimeToStr(Now)); end; procedure TForm1.FormCreate(Sender: TObject); begin //COM сервер отображает себя на экране WordApplication1.Visible:=true; end; :00:2218 Подключение к существующему интерфейсу Пример 3
Классы – наследники ToleObject TOleServer = class(TComponent, IUnknown) TWordApplication = class(TOleServer) Благодаря такому объявлению, класс TwordApplication наследует свойства и методы класса Tcomponent ( способен устанавливаться на палитре компонент и прочие …), а так же знает все о доступе к интерфейсам COM серверов, благодаря наследованию интерфейса IUNknown. В библиотеке типов прописаны все доступные методы и свойсва COM сервера. Когда создается контроллер автоматизации, то приложение получает доступ к Dual Interface описанный в библиотеке типов. Dual интерфейс – есть совокупность пользовательского интерфейса, описанного в библиотеке типов и dispinterface, который доступен в момент выполнения приложения. Это реализовано с помощью COM VTABLE интерфейса, унаследованного от IDISPATCH. Использование Vtable интерфейса имеет ряд преимуществ : 1) Передаваемые параметры, их типы и количество проверяется на стадии проектирования и редактор кода сопровождает разработчика всевозможными хинтами и подсказками. 2) Через Vtable интерфейс осуществляется значительно более быстрый доступ к серверу автоматизации, чем через DispInterface В то же время, не всегда разработчик получает доступ к библиотеке типов и соответственно к V – таблице, поэтому приходится иногда пользоваться Idispatch интерфейсом :00:2219
:00:2220 ДОСТУП К ФУНКЦИЯМ MS EXCEL
Тестовый проект :00:2221
Позднее связывание (OLE) procedure TMainForm.BtExcelOLEClick(Sender: TObject); Var i,j : integer; ExcelApplication:Variant; begin Application.Minimize; try ExcelApplication:=CreateOleObject('Excel.Application'); ExcelApplication.visible:=true; ExcelApplication.WorkBooks.Add; except ShowMessage(' Ошибка инициализации Excel'); exit; end; for i:=0 to SG.ColCount-1 do for j:=0 to SG.RowCount-1 do ExcelApplication.Cells[j+1,i+1].value:=SG.Cells[i,j]; Application.Restore; ShowMessage(' Экспорт завершен '); end; :00:2222
Связывание через Dual интерфейс procedure TMainForm.BtExcelServersClick(Sender: TObject); var WorkBk : _WorkBook; // определяем WorkBook WorkSheet : _WorkSheet; // определяем WorkSheet i,j : integer; begin XLApp.Connect; // Добавляем WorkBooks в ExcelApplication XLApp.WorkBooks.Add(xlWBatWorkSheet,0); // Выбираем первую WorkBook WorkBk := XLApp.WorkBooks.Item[1]; // Определяем первый WorkSheet WorkSheet := WorkBk.WorkSheets.Get_Item(1) as _WorkSheet; for i:=0 to SG.ColCount-1 do for j:=0 to SG.RowCount-1 do Worksheet.Cells.Item[j+1,i+1].value:=SG.Cells[i,j]; XLApp.Visible[1]:=true; Application.Restore; ShowMessage(' Экспорт завершен '); end; :00:2223
Передача массива через Dual интерфейс var WorkBk : _WorkBook; // определяем WorkBook WorkSheet : _WorkSheet; // определяем WorkSheet TabGrid : Variant; i,j,r,c : integer; begin r:=SG.RowCount; c:=SG.RowCount; // Заполняем TabGrid TabGrid := VarArrayCreate([0,(r-1),0,(c-1)],VarOleStr); for i:=0 to SG.ColCount-1 do for j:=0 to SG.RowCount-1 do TabGrid[j,i]:=SG.Cells[i,j]; XLApp.Connect; // Добавляем WorkBooks в ExcelApplication XLApp.WorkBooks.Add(xlWBatWorkSheet,0); // Выбираем первую WorkBook WorkBk := XLApp.WorkBooks.Item[1]; // Определяем первый WorkSheet WorkSheet := WorkBk.WorkSheets.Get_Item(1) as _WorkSheet; Worksheet.Range['A1', Worksheet.Cells.Item[R,C]].Value2 := TabGrid; XLApp.Visible[1]:=true; Application.Restore; ShowMessage(' Экспорт завершен '); end; :00:2224
Заполнение шаблона :00:2225 OleReport: TOleContainer;
Текст процедуры заполнения var WorkBk : _WorkBook; WorkSheet : _WorkSheet; TabGrid : Variant; i,j,r,c : integer; Path : string; begin r:=SG.RowCount; c:=SG.RowCount; TabGrid := VarArrayCreate([0,(r-1),0,(c-1)],VarOleStr); for i:=0 to SG.ColCount-1 do for j:=0 to SG.RowCount-1 do TabGrid[j,i]:=SG.Cells[i,j]; Path:=ExtractFilePath(Paramstr(0))+'shablon.xls'; XLApp.Connect; XLApp.Workbooks.Open(Path, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, 0); WorkBk:=XLApp.ActiveWorkbook; WorkSheet := WorkBk.WorkSheets.Get_Item(1) as _WorkSheet; Worksheet.Range['B3', 'F7'].Value2 := TabGrid; path:=ExtractFilePath(Paramstr(0))+'report.xls'; WorkBk.SaveCopyAs(path,0); WorkBk.Close(false,0,0,0); XLApp.Disconnect; // Разрываем связь с сервером // Отображаем полученный отчет у себя на форме. OleReport.CreateObjectFromFile(path,false); OleReport.Visible:=true; ShowMessage(' Экспорт завершен '); end; :00:2226