На этом шаге мы рассмотрим проблемы, решаемые с помощью интерфейсов.
Термин "COM" представляет собой сокращение фразы Component Object Model. И эта фраза очень точно передаёт основные идеи COM-технологии - экспорт объектов. Идея экспорта объектов заключается в том, что один модуль создает объект, а другой его использует посредством обращения к методам (сервисам). Предположим, что где-то и кем-то был реализован достаточно сложный алгоритм распознавания текста в файле *.bmp, получаемом при сканировании документов. Конечно же, производители сканеров захотят предоставить дополнительные возможности покупателям и пожелают включить такое программное обеспечение в свой пакет. При этом любая фирма будет стараться свести к минимуму число приложений в своём пакете: по возможности все сервисы должны вызываться из одного приложения.
На первый взгляд эта проблема решается достаточно просто, по крайней мере когда в качестве другого модуля используется загружаемая библиотека - DLL. В этом случае оба модуля содержатся в одном и том же адресном пространстве. Казалось бы, создадим в DLL объект, вызовем его методы из основного приложения - данная задача решена. Чтобы понять, что это не так, достаточно рассмотреть небольшой пример. Определим метод IsFont в приложении:
procedure IsFont(O:TObject); begin if O is TFont then ShowMessage('Font') else ShowMessage('Not font'); end; procedure TForm1.Button1Click(Sender: TObject); begin IsFont(Font); end;
Вызовем этот метод из приложения. В качестве параметра метода IsFont используем свойство Font формы. В результате будет показано сообщение, что объект является шрифтом.
Приведем текст приложения:
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.dfm} procedure IsFont(O:TObject); begin if O is TFont then ShowMessage('Font') else ShowMessage('Not font'); end; procedure TForm1.Button1Click(Sender: TObject); begin IsFont(Font); end; end.
Теперь создадим динамически загружаемую библиотеку DLL, поместим в нее метод IsFont и объявим его экспортируемым:
library FontLib; { Important note about DLL memory management: ShareMem must be the first unit in your library's USES clause AND your project's (select Project-View Source) USES clause if your DLL exports any procedures or functions that pass strings as parameters or function results. This applies to all strings passed to and from your DLL--even those that are nested in records and classes. ShareMem is the interface unit to the BORLNDMM.DLL shared memory manager, which must be deployed along with your DLL. To avoid using BORLNDMM.DLL, pass string information using PChar or ShortString parameters. } uses SysUtils, Dialogs, Graphics, Classes; {$R *.res} procedure IsFont(O:Tobject); export; begin if O is Tfont then ShowMessage('Font') else ShowMessage('Not font'); end; exports IsFont name 'IsFont'; begin end.
Результат этого действия окажется противоположным - теперь мы получим сообщение о том, что данный объект не является шрифтом. Таким образом, если использовать традиционное объектно-ориентированное программирование, то нельзя создать объект в одном модуле, а вызывать его методы в другом. В частности, оператор IS всегда возвратит False, а использование оператора AS приведёт к исключению.
Приведем текст приложения:
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.dfm} procedure IsF(O:Tobject); external 'FontLib.dll' name 'IsFont'; procedure TForm1.Button1Click(Sender: TObject); begin IsF(Font); end; end.
Вторая проблема возникает при обобщении первой проблемы. В приведенном выше примере было известно заранее, какой метод надо вызвать и каков список его параметров. Соответственно в приложении перед компиляциеей программистом помещается строка кода:
procedure IsF(O:Tobject); external 'FontLib.dll' name 'IsFont';
Эта строка определяет имя метода (IsFont) и список его параметров (O:TObject). Соответственно программисту, использующему эту библиотеку, потребуется иметь под рукой документацию - список методов и список их формальных параметров. Их реализация "вручную" неизбежно приведёт к ошибкам. Для исправления ошибок потребуется время, и при этом нет никакой гарантии, что все они будут обнаружены. Соответственно хотелось бы, чтобы сам объект мог информировать среду разработки о том, какие методы ему доступны и каковы списки их формальных параметров.
Этим не ограничиваются сложности работы с различными модулями. Например, если в одном из модулей была зарезервирована память для хранения данных, то в другом модуле нельзя ни освободить ее, ни изменить ее размер. Это связано с тем, что различные модули имеют разные менеджеры памяти. Если один модуль выделяет память, то в менеджере памяти другого модуля это никак не фиксируется. Для таких операций необходимо, чтобы был общий менеджер памяти. В Delphi используется модуль ShareMem для создания совместного менеджера.
Еще одна проблема возникает при обращении к объекту, созданному другим приложением. В этом случае указатель на объект памяти, созданный в одном приложении, является не действительным для другого приложения. При передаче указателя из одного приложения в другое (это можно сделать, скопировав его в Clipboard или используя метод PostMessage), другое приложение будет обращаться совсем не к тем ячейкам оперативной памяти компьютера, где реально находятся данные. В лучшем случае сразу же произойдет исключение, в худшем - при попытке записи данных - будет разрушено ядро Windows. Эту проблему можно представить более глобально, если рассматривать возможность создания объекта на одном из компьютеров, а использовать его с помощью сети на другом.
Следующая проблема возникает при передаче данных от одного приложения к другому - например, через двоичный файл. Предположим, имеются два приложения - одно написано на языке Фортран, другое - на Delphi. И в Фортране, и в Delphi имеются двухбайтовые целочисленные переменные со знаком и динамическим диапазоном от -32768 до +32767. На первый взгляд для передачи данных достаточно считать данные из двоичного файла, созданного одним приложением, в другое приложение. Но при выполнении такой операции можно обнаружить, что данные оказываются искаженными. Причина заключается в различном представлении переменных в этих двух языках программирования. В Delphi битовое представление числа -1 выглядит следующим образом: 1000000000000001, а в Фортране - 1111111111111110 (not 1). Эту проблему можно сформулировать так: в различных языках программирования допускается различное представление одних и тех же данных. Соответственно они по-разному обрабатываются. При сложении двух целых чисел компилятор Delphi генерирует команду процессора ADD, а компилятор Фортрана - AND.
Можно выделить ещё одну проблему, которая встречается в традиционном программировании. Взаимодействие двух приложений подразумевает, что они выполняются одновременно. Это означает, что одно из приложений должно запустить другое или потребовать у пользователя запуск другого приложения. Эта ситуация прекрасно наблюдается при использовании обмена данными с помощью протокола DDE (Dynamic Data Exchange). Пользователь обязательно должен запустить DDE-сервер вручную - иначе клиентское приложение не сможет работать. Это требует дополнительных операций и вызывает естественную негативную реакцию пользователя, что отрицательно сказывается на маркетинге приложений, являющихся клиентами DDE. Все эти проблемы решаются с помощью COM-технологии. Проблемы вызова методов объектов, освобождения и резервирования памяти, унифицированного представления данных решаются с помощью интерфейсов. Проблема предоставления среде разработки информации о названии методов объектов и списков формальных параметров решается при помощи библиотек типов. Проблема передачи указателя из адресного пространства одного приложения в адресное пространство другого приложения решается с помощью маршрутизации. Маршалинг используется и для работы с объектами, созданными на других компьютерах. И наконец, проблему автоматического запуска сервера решает использование фабрики класса и ее регистрация системном реестре. В следующих шагах приведены более детальные описания методов решения вышеперечисленных проблем.
На следующем шаге мы рассмотрим понятие интерфейса.