Midas и COM. Советы и Приемы

Статья показывает, как писать приложения, использующие несколько модулей, которые связываются через COM и совместно используют Midas сервер. Освещаются вопросы перемещения файлов, массивов и других структур данных c использованием COM.
Вы можете не нуждаться в распределенных приложениях, но вы нуждаетесь в Midas
Borland разрабатывал Midas как инструмент для создания многоуровневых распределенных приложений. Но Midas - лучший способ построить любое приложение баз данных, особенно большое приложение, даже, когда Вы не нуждаетесь в распределенной базе данных.
== Транзакции в настольных базах данных ==
Если Вы работаете с Paradox или Dbase таблицами, и нуждаетесь в поддержке транзакций, Вы ограничены, потому что единственный уровень изоляции транзакции read uncommited (также грязное чтение). Вдобавок ко всему, невозможно произвести откат при возникновении аварийной ситуации , т.е аварийный отказ может оставлять вашу базу данных в несогласованном состоянии. Однако, если Вы используете ClientDataSet, Вы действительно получаете транзакции и автоматический откат в аварийных ситуациях.
ClientDataSets имеет копию данных в памяти и Вы фактически работаете с локальной копией базы. Вы не будете видеть вставки, удаления или модификаций, сделанных другими пользователями или другими наборами данных в том же самом приложении. Это дает Вам обеспечение изоляции транзакции. Независимо от того сколько раз Вы просматриваете ваши данные, Вы будете всегда видеть снимок данных, с которым Вы начали работать.
ClientDataSet содержит все изменения в памяти в свойстве Delta, пока Вы не вызовете ApplyUpdates. Это означает, что при аварии произойдет откат транзакции, потому что все изменения в Delta будут потеряны. Единственный недостаток в использовании ClientDataSet, то, что аварийный отказ, во время выполнения ApplyUpdates, может все еще оставлять вашу базу данных в несогласованном состоянии. Однако, Если Вы вызываете ApplyUpdates достаточно часто достаточно, чтобы гарантировать, что только несколько записей модифицируются, модификация происходит в течении долей секунды. Это - намного меньшее окно уязвимости, чем использование локальных транзакций, где база данных может находится в несогласованном состоянии с момента первого изменения до проведения или отката транзакции. Для пользователя, вручную вносящего изменения, это время может быть несколько минут.
== Улучшение параллельности работы баз данных ==
Одна из проблема транзакциями в любой базе данных состоит в том, что транзакции, которые являются активными в течение длительного времени, уменьшают способность другого пользователя модифицировать базу данных. Это случается, потому что каждый раз измененная запись должна быть блокирована, и блокировка должна быть задержана, пока транзакция не завершится, чтобы гарантировать, что никакой другой пользователь не может изменять запись. Эта проблема особенно актуальна, если база данных использует блокировку страницы, это делает невозможным для других пользователей изменение строк на блокированной странице.
Кэшируемые изменения были первоначально добавлены к Delphi, чтобы преодолеть эту проблему, и ClientDataSet является развитием этой идеи. Если Вы используете ClientDataSets, чтобы редактировать ваши данные, изменения сохранятся в локальной копии данных - в кэше ClientDataSet. База данных не содержит сделанных изменений, пока Вы не вызываете ApplyUpdates. Так как транзакция активна на сервере только, во время обращения к ApplyUpdates т.е обычно на доли секунды, блокировки не задерживается в течение длительного времени, и параллелизм улучшается.
== Обеспечение нескольких платформ серверов баз данных ==
Предположим, что Вы пишете приложение для рынка. Вы знаете, что некоторые из ваших потенциальных заказчиков уже выбрали платформу сервера базы данных, так что Вы должны создать версии вашего приложения, для выполнения на Oracle, Microsoft SQL и Interbase. Компоненты, используемые, для работы с базами данных различны. Для Interbase самый лучший выбор Interbase Express, для MS SQL , самый лучший выбор - ADO Express и для Oracle, Вы можете использовать или BDE или ADO Express.
Midas делает создание таких приложений намного проще. Для каждой базы данных разрабатывают Midas приложение - сервер, которое содержит только специфический код для одного типа базы данных. Вся логика приложения находится в клиентском приложении. Это позволяет Вам, реализовать большую часть кода, общего для всех типов баз данных в Midas клиенте, а Midas сервер, будет обеспечивать только соединение с конкретным типом базы данных. Вы будете иметь общего Midas клиента и Midas сервер, который соответствует базе данных, которую использует клиент. Другая проблема, которую это решает, имеет место, если Вы настроили приложение для специфического клиента, и клиент затем решает заменить платформу базы данных. С Midas все, что Вы должны сделать - установить Midas сервер, который работает с новой платформой. Никакие изменения для настроенного клиента не требуются.
== Создание модульных приложений ==
Объединение Midas с COM позволяет, создавать большие сложные приложения из множества COM серверов, которые совместно используют общее соединение базы данных. Использование Midas и COM вместе:
* Делает групповую разработку проще, позволяя каждому члену группы работать над модулем, который может компилироваться и тестироваться независимо.
* Делает многомодульные приложения проще - устанавливаются только те модули, которые нужны пользователю.
* Допускает, чтобы все модули совместно использовали общее соединение базы данных.
* Делает модули доступными независимо от языка программирования, который используется.
* Делает проще поддержку баз данных.
Следующие разделы этой статьи посвящены разработке простого приложения, которое показывает совместное использование Midas и COM. Также рассмотрены обратные вызовы от COM сервера к клиенту. Мы разработаем модульное приложение. Создадим очень простой пример, который состоит из Midas сервера и двух Midas клиентов. Первый Midas клиент будет главной формой приложения, отображающей данные из таблиц Customer и Order. Это приложение - EXE. Второй Midas клиент отобразит данные из таблицы Order и выполнен как внутренний сервер автоматизации. Midas сервер также выполнен как DLL внутреннего сервера автоматизации. Роли трех модулей могут немного смутить Вас. Чтобы разъяснить кто, что делает следующая таблица показывает каждое приложение и его назначение
Приложение 	Назначение 	Роль 	Реализация
DemoDllServer	Обеспечивает соединение с базой данных	Midas сервер 	ActiveX Library DLL
DemoClient 	Содержит форму для таблицы Customer 	Midas клиент, COM клиент	EXE
DemoOrders 	Содержит форму таблицы Order 	Midas клиент, COM сервер	ActiveX Library DLL
== Разработка Midas сервера ==
Midas сервер имеет только одну необычную возможность. Он выполнен как DLL, так что не будет отображать форму или показывать иконку в панели задач. В то время как наличие отображаемой формы сервера может быть допустимым для распределенной системы, где никто обычно не видит экран машины - Midas сервера, это - плохая идея для приложения, где сервер и клиент выполнится на одном PC, потому что пользователь может быть введен в заблуждение дополнительной иконкой и может пробовать закрывать сервер. Решение состоит в том, чтобы выполнить Midas сервер как DLL, так чтобы он не имел никакого интерфейса пользователя. При выполнении Midas сервера в виде DLL также улучшается производительность. Для создания Midas сервера как DLL, выберем меню File | New из меню и затем закладку ActiveX репозитария объектов. Двойным щелчком левой кнопки мыши на иконке ActiveX Library создаст новый проект ActiveX библиотеки. С тех пор как Midas использует COM, чтобы обработать связь между Midas клиентом и Midas сервером, ActiveX библиотека используется, чтобы обеспечить требуемую поддержку COM.
После этого процесс создания сервера в виде DLL ничем не отличается от разработки EXE. Выберите File | New, перейдите к странице Multitier, и добавьте Remote Data Module (удаленный модуль данных ) к проекту. Рисунок 1 показывает удаленный модуль данных для типового приложения.
Это приложение написано в типичном стиле клиента / сервера. Когда пользователь открывает приложение, никакие данные не отображаются. Взамен пользователь должен ввести некоторые критерии выбора(выделения), которые выберут приемлемое число записей. Чтобы реализовать этот подход, предложение SQL для CustomerQry компонента:
SELECT * FROM Customer
WHERE CustNo = -1
Это позволяет открывать Customer ClientDataSet в приложении DemoClient немедленно без отображения любых данных, т.к нет никаких записей в таблице Customer, чей номер заказчика является отрицательным. И DataSetProvider (CustomerProv) и DataSource (CustomerSrc) соединены с CustomerQry, установкой их свойств DataSet в CustomerQry. В свойстве Options DataSetProvider poAllowCommandText установлен в True, так что клиентское приложение может изменять свойство SQL CustomerQry, чтобы выбрать различные записи из таблицы Customer. OrdersQry обеспечивает выборку записей текущего заказчика. Свойство SQL установлено в:
SELECT * FROM Orders
WHERE (CustNo =:CustNo)
Свойство DataSource установлено в CustomerSrc, таким образом значение параметра :CustNo будет обеспечено в соответствии с текущей записью в CustomerQry. Это приводит к сохранению в наборе данных Customer данных о заказах, в виде вложенного набора данных.
Приложение DemoOrders позволяет пользователю искать записи в таблице Order по номеру заказа или всем заказы по номеру заказчика. Чтобы обеспечивать доступ к всем заказам необходим второй компонент TQuery - OrdersAllQry, который не связан с CustomerQry. Свойство SQL установлено, чтобы не выбирать никаких записей, т.е номер заказа - минус один. DataSetProvider для OrdersAllQry также имеет poAllowCommandText равный True. Так как этот Midas сервер - DLL, Вы не можете зарегистрировать его, запустив на выполнение. Вместо этого, выберите Run | Register ActiveX Server из меню Delphi, чтобы cкомпилировать и затем зарегистрировать Midas сервер.
Midas сервер в типичных трех уровнях распределенных приложениях не, только обеспечивает соединение с базой данных, но может также обеспечивать бизнес логику или другие сервисы. Однако, в этой статье мы обсуждаем приложение, состоящее из нескольких модулей. Все модули будут Midas клиентами, использующими один Midas сервер и и клиент, и сервер работают на той же самой машине. Предположим, что Вы пишете приложение, используя эту архитектуру. Если Вы должны поддерживать несколько видов серверов баз данных, Вы можете захотеть ограничить код Midas сервера только, тем кодом, который является специфическими для специфической базы данных, типа Oracle или Microsoft SQL Server, и хранить весь общий для баз данных код клиентских модулях. Это позволит Вам, поддерживать несколько Midas серверов для различных баз данных без дублирования кода.
== Разработка COM Клиента ==
Рисунок 2 показывает главную форму приложения. Она состоит из двух DBGRID и двух DBNAVIGATOR. Верхний DBGRID и DBNAVIGATOR отображают информацию о заказчике и нижний DBGRID и DBNAVIGATOR - таблицу заказов.
Модуль данных содержит компонент DCOMCONNECTION, два ClientDataSet и два DataSources. Имя DCOMCONNECTION компонента - DemoConn, и св-во ServerName установлено в DemoDllSrvr.DllDemoServer. Свойство RemoteServer CustomerCds установлено в DemoConn, и ProviderName установлено равным CustomerProv. Свойство DataSetField компонента OrdersCds установлено в CustomerCdsOrdersQry, чтобы получить данные из вложенного набора данных. Меню Edit содержит диалог поиска, который отображает диалог, показанный на рисунке 4. Диалог дает возможность выбирать заказчика по номеру или выбирать все записи с определенным состоянием, используя метод FindCustomer в модуле данных CustomerDm. Реализацию данного метода Вы можете посмотреть в коде приложения.
Меню File на основной форме содержит пункт вызова формы выбора заказов, форма позволяет искать любой заказ по номеру заказчика или номеру заказа. Сетка заказов соединена со всплывающим меню, которое имеет два пункта. Первый, отобразить текущий заказ, открывает форму заказов и показывают текущую запись заказа. Второй, все заказы данного заказчика.
== Создание COM сервера ==
Теперь начинается самое интересное. Следующий наш шаг - создание формы заказов и методов, которые форма просмотра заказчиков будет использовать, чтобы открыть форму заказов, искать заказы заказчика искать заказ по его номеру. Однако, форма заказов будет расположена в отдельном приложении, которое является сервером Автоматизации, и форма заказчиков вызовает методы формы заказов через интерфейс автоматизации.
Чтобы создать приложение Orders, выберите закладку ActiveX репозитарии и двойным щелчком мыши пункт ActiveX Library. Добавьте форму и модуль данных к приложению. Законченная форма изображена на рисунке 5 и модуль данных на рисунке 6.
Компонент DCOMCONNECTION на рисунке 6, OrdersConn, соединяется с Midas сервером, DemoDllSrvr.DllDemoServer, так же как компонент DCOMCONNECTION модуле данных заказчиков. Свойство RemoteServer OrdersCds установлено в OrdersConn, и ProviderName установлено в OrdersAllProv.
Следующий шаг превратит эту DLL в сервер автоматизации. Вернитесь к закладке ActiveX репозитария, двойной щелчок на мастере построения объекта автоматизации (ActiveX Object), и введите значение OrdersServer для имени CoClass. Также проверьте установку переключателя Generate Event Support Code. В редакторе библиотеки типов (Type Library Editor) добавьте методы к интерфейсу IORDERSERVER (таблица) и затем нажмите кнопку Refresh.
Метод 	Параметры	Возвращаемое значение
FindByOrderNo 	OrderNo 	long
FindByCustNo 	CustNo 	long
OpenOrdersForm	 	
CloseOrders 	 	
FindCustomer 	 	
GetCustNo 	CustNo 	Variant *
procedure TOrderServer.FindByOrderNo(OrderNo: Integer);
begin
 OrderDm.FindByOrderNo(OrderNo);
end;
procedure TOrderServer.FindByCustNo(CustNo: Integer);
begin
 OrderDm.FindByCustNo(CustNo);
end;
procedure TOrderServer.OpenOrdersForm;
begin
 OrderDm := TOrderDm.Create(nil);
 OrderForm := TOrderForm.Create(nil);
 OrderForm.Show;
end;
Рис.7. Методы FindByOrderNo, FindByCustNo, OpenOrdersForm
implementation
uses FindOrderF;
{$R *.DFM}
{ Displays the Find Order dialog. Calls the appropriate find method based on
 which edit box on the Find Order dialog has a value.}
procedure TOrderDm.FindOrder;
begin
 FindOrderForm := TFindOrderForm.Create(Self);
 try
 with FindOrderForm do
 begin
 ShowModal;
 if OrderNoEdit.Text <> then
 FindByOrderNo(StrToInt(OrderNoEdit.Text))
 else if CustNoEdit.Text <> then
 FindByCustNo(StrToInt(CustNoEdit.Text))
 else
 MessageDlg('You must enter an order number or customer number.', mtError, [mbOK], 0);
 end; //with
 finally
 FindOrderForm.Free;
 end; //try
end;
{ Finds an Order record given its OrderNo. }
procedure TOrderDm.FindByOrderNo(OrderNo: Integer);
begin
 with OrdersCds do
 begin
 Close;
 CommandText := 'SELECT * FROM Orders WHERE ' +
 '(OrderNo = ' + IntToStr(OrderNo) + ')';
 Open;
 end;
end;
Рис.8. Методы модуля данных OrdersDM
{ Finds all of the Order records for the specified Customer. }
procedure TOrderDm.FindByCustNo(CustNo: Integer);
begin
 with OrdersCds do
 begin
 Close;
 CommandText := 'SELECT * FROM Orders WHERE ' + '(CustNo = ' + IntToStr(CustNo) + ')';
 Open;
 end;
end;
Первые два метода, FindByOrderNo и FindByCustNo вызывают методы с тем же самым именем в модуле данных заказов. Реализация модуля данных заказов приведена на рисунке 8. Оба метода закрывают ClientDataSet заказов, устанавливают новое SQL предложение в тексте команды и затем вновь открывают ClientDataSet. При открытии ClientDataSet значение в CommandText передается к Midas серверу и затем в свойство SQL компонента OrdersAllQry прежде, чем запрос будет открыт. Программа работы с заказчиками вызывает эти методы для поиска по номеру заказа или для вывода всех заказов, сделанных заказчиком. Третий метод, OpenOrdersForm, создает модуль данных, OrderDm, и отображает форму заказов OrdersForm. Программа работы с заказчиками вызывает этот метод для отображения формы заказов.
FindOrder метод модуля данных заказов вызывается из меню Edit формы заказов. При этом отображается диалоговое окно FindOrdersForm, которое позволяет пользователю найти один или большее количество заказов по номеру или по номеру заказчика.
== Обратный вызов COM клиенту ==
Используя вышеописанные методы наше приложение, может вызывать методы на COM сервере, чтобы сформировать заказ и найти заказы по номеру или по принадлежности к заказчику. Однако, COM сервер должен быть способным передавать данные клиенту по двум причинам. Первая, когда пользователь просматривает заказ, он должен иметь возможность отобразить запись заказчика для этого заказа. Другими словами, форма заказов должна уметь сообщить форме заказчика о необходимости найти нужного заказчика и отобразить себя. Вторая проблема состоит в том, что COM сервер показывает форму заказов в режиме modeless. Это означает, что COM клиент не может знать о закрытии COM сервер. Единственное решение состоит в том, что COM сервер должен сообщить COM клиенту, когда пользователь закрывает форму заказов.
Имеются три способа для связи сервера с клиентом.
* Первый, добавить объект автоматизации к клиентскому приложению, так чтобы сервер мог соединяться с клиентом и вызывать методы интерфейса объекта автоматизации. Это означает, что приложение, которое содержит форму заказчика является, и COM клиентом, и COM сервером по отношению к DLL заказов и DLL является, и клиентом и сервером по отношению к приложению заказчика.
* Второй метод включает создание интерфейса обратного вызова к клиентскому приложению. Чтобы сделать это, Вы должны добавить интерфейс клиенту и создавать экземпляр объекта, который реализует интерфейс. Когда COM клиент соединяется с COM сервером создается экземпляр объекта обратного вызова, затем вызывают метод COM сервера и передают ссылку на интерфейс, как параметр, к COM серверу. Используя эту ссылку сервер может вызывать методы клиента.
* Третья методика позволяет серверу инициировать события на клиенте через dispinterface сервера. Это самый простой способ при реализации в Delphi 5 благодаря мастерам, которые делают большинство работы. Хотя данная методика имеет некоторые ограничения, она пригодна для большинства приложений, так что это - метод, используемый в этой статье. Ключ к использованию событий - установка переключателя Generate Event Support Code при добавлении объекта автоматизации к COM серверу. Установка переключателя добавляет два интерфейса к библиотеке типов COM сервера. Мы уже добавили методы первого интерфейса, IORDERSERVER. Второй интерфейс - dispatch интерфейс IORDERSERVEREVENTS. Настало время, чтобы открыть Редактор Библиотеки Типов снова и добавить два метода к интерфейсу IorderServerEvents. Первый OnCloseOrders и второй OnFindCustomer. После добавления события OnFindCustomer выберем закладку параметров, затем нажимаем кнопку Add, чтобы добавить новый параметр. Назовите параметр CustNo, тип Long. OnCloseOrders событие будет возникать, когда пользователь закрывает форму заказов, извещая COM клиента о возможности закрытия соединения с заказами на COM сервере. OnFindCustomer событие будет возникать, когда пользователь выбирает пункт меню View | Customer . Это событие сообщит COM клиенту, что необходимо найти и отобразить запись заказчика, чей номер соответствует номеру заказчика текущей записи заказа.
Рис.9. Генерация событий
procedure TOrderServer.CloseOrders;
begin
 FEvents.OnCloseOrders;
end;
procedure TOrderServer.FindCustomer;
begin
 FEvents.OnFindCustomer(OrderDm.OrdersCdsCustNo.AsInteger);
end;
Код обработчиков событий приведен на рисунке 9. CloseOrders и FindCustomer - методы, которые были добавлены к IORDERSERVER ранее. CloseOrders вызывается из обработчика события OnDestroy формы заказов. FindCustomer вызывается из обработчика события OnClick пункта меню View | Customer.
Чтобы вызывать эти методы, Вы должны иметь ссылку на объект OrderServer. Чтобы получать эту ссылку, сделаны два изменения, показанные на рисунках 10 и 11, в модуле OrdersAuto. Глобальная переменная OrderServer добавлена к разделу интерфейса модуля. Добавлена строка в методе Initialize объекта TORDERSERVER, устанавливающая глобальную переменно OrderServer в Self. Переменная OrderServer теперь обеспечивает ссылку на объект автоматизации OrderServer, который может использоваться, чтобы вызвать методы из формы Заказов в обработчике OnDestroy и обработчиком OnClick пункта меню или в любом месте приложения DemoOrders. Обратите внимание, что, если Вы только хотите возбуждать событие из метода в интерфейсе IORDERSERVER, Вы можете опускать эти два изменения. Мы нуждались в ссылке к объекту Automation только, потому что мы нуждались в генерации событий из в любом месте приложения.
var
 OrderServer: TOrderServer;
Рис.10. Объявление переменной ссылки на объект автоматизации
procedure TOrderServer.Initialize;
begin
 inherited Initialize;
 FConnectionPoints := TConnectionPoints.Create(Self);
 if AutoFactory.EventTypeInfo <> nil then
 FConnectionPoint := FConnectionPoints.CreateConnectionPoint(
 AutoFactory.EventIID, ckSingle, EventConnect)
 else
 FConnectionPoint := nil;
 OrderServer := Self;
end;
Рис.11. Инициализация ссылки на OrderServer
Последний шаг реализация события в COM клиенте. В проекте DemoClient, открытом в IDE, выберите Project | Import Type Library из меню, чтобы отобразить диалог импорта, показанный на рисунке 12. Выберите библиотеку DemoOrders в окне списка, и удостоверьтесь, что флажок Generate Component Wrapper установлен. Это создаст компонент, типа TORDERSERVER, и добавит его к вашей палитре компонентов. Когда Вы нажмете кнопку Install, Вас будут спрашивать, хотите ли Вы устанавливать этот компонент в новый пакет или существующий пакет. Вы возможно найдете это более удобным поместить все компоненты сервера для проекта, над которым Вы работаете в их собственном пакете. Чтобы вы не делали, не устанавливайте этот компонент в существующие пакеты компонентов Delphi. Выбрав щелкните пакета ОК, затем Yes в диалоге, сообщающем Вам, что пакет будет сформирован и затем установлен. Компонент, который создан - оболочка вокруг COM сервера и может использоваться, чтобы соединиться с сервером и вызывать методы. Компонент OrderServer также имеет событие для каждого события, которое Вы добавили к интерфейсу IORDERSERVEREVENTS в COM сервере.
Киньте TORDERSERVER компонент на форму заказчика, и назовите его OrderServer. Установите свойство AutoConnect в False, так что соединение с COM сервером не будет открыто автоматически, когда программа стартует. Переключитесь на закладку Events инспектора объектов, и создайте обработчики событий OnCloseOrders и OnFindCustomer . Код для обоих обработчиков события показывается на рисунке 13.
procedure TCustomerForm.OrderServerCloseOrders(Sender: TObject);
begin
 OrderServer.Disconnect;
end;
procedure TCustomerForm.OrderServerFindCustomer(Sender: TObject;
 CustNo: Integer);
begin
 CustomerDm.FindByCustNo(CustNo);
 Show;
end;
Рис. 13 Обработчики событий
Осталось реализовать обработчик события OnClick для меню File | Orders и всплывающее меню сетки Orders. Код для этих обработчиков события показывается на рисунке 14.
procedure TCustomerForm.Orders1Click(Sender: TObject);
begin
 OrderServer.Connect;
 OrderServer.OpenOrdersForm;
end;
procedure TCustomerForm.ShowThisOrder1Click(Sender: TObject);
begin
 with OrderServer do
 begin
 Connect;
 OpenOrdersForm;
 FindByOrderNo(CustomerDm.OrdersCds.FieldByName('OrderNo').AsInteger);
 end; //with
end;
procedure TCustomerForm.ShowAllOrdersForThisCustomer1Click(Sender: TObject);
begin
 with OrderServer do
 begin
 Connect;
 OpenOrdersForm;
 FindByCustNo(CustomerDm.OrdersCds.FieldByName('CustNo').AsInteger);
 end; //with
end;
Рис.14. Обработчик пункта меню
== Перемещение Данных Между Сервером и Клиентом ==
Что Вы делаете, когда хотите переместить данные, который не сохранены в таблице базы данных между COM сервером и COM клиентом? Заполните Variant, и передайте это как параметр. Обратите внимание, что я не говорю Midas сервера и клиента, а любого COM сервера и клиента. В то время как некоторые из методов в этом разделе будут демонстрироваться с Midas сервером, и клиент, будет использовать интерфейс IAPPSERVER они будут работать в равной степени хорошо работать между любым COM сервером и клиентом, использующим любой интерфейс.
=== Передача Табличных Данных ===
Если Вам необходимо передать табличные данные, самый простой способ - это использовать ClientDataSet и передать данные как показано в приложении PassData. Это приложение состоит из COM сервера и COM клиента. Основная форма клиента, показанная на рисунке 15, содержит Database, Query, DataSetProvider, ClientDataSet и DataSource, соединенный с DBGRID для отображения данных таблицы заказчика из DBDEMOS. Обработчик события OnClick кнопки Send Data показан на рисунке 16.
procedure TMainForm.SendBtnClick(Sender: TObject);
begin
 PassDataServer := CoPassData.Create;
 PassDataServer.PassData(CustCds.Data);
end;
Рис.16. Обработчик события нажатия кнопки Send Data
Клиентское приложение использует модуль интерфейса библиотеки типов сервера, так что можно соединяться с сервером, вызывая coclass's сервера, использовать метод Create и получить ссылку интерфейс. PassDataServer объявлена как закрытая переменная формы типа - IPASSDATA. IPASSDATA - интерфейс, реализованный COM сервером. Вторая строка вызывает метод PassData интерфейса IPASSDATA и передает свойство Data ClientDataSet как параметр.
Рисунок 17 показывает метод PassData сервера. Этот метод получает параметр типа OleVariant, который используется, чтобы передать свойство Data ClientDataSet от клиента к серверу. Форма главного приложения сервера содержит ClientDataSet, DataSource и DBGRID. Код на рисунке 17 присваивает значение параметра CdsData свойству Data ClientDataSet и открывает ClientDataSet.
procedure TPassData.PassData(CdsData: OleVariant);
begin
 with MainForm.CustCds do
 begin
 Data := CdsData;
 Open;
 end; // with
end;
Рис.17. Метод PassData
Если необходимо передать изменения, которые были сделаны пользователем в ClientDataSet, содержащиеся в свойстве Delta ClientDataSet, добавьте другой параметр OleVariant, и присвойте дельту этому параметру. К сожалению, свойство Delta - доступно только для чтения, так что Вы не можете назначать параметр Delta свойству Delta ClientDataSet. Обратите внимание, что ClientDataSet не соединен с удаленным сервером или провайдером в этом примере, хотя это и возможно.
=== Передача данных Flat файла ===
Одна из интересных особенностей Midas - то, что данные, которые Midas сервер посылает клиенту, могут храниться где угодно. То есть не обязательно в таблице базы данных. Один из методов в приложении выборки PassOther обеспечивает данные Midas клиенту из CSV файла ASCII. Самый простой способ делать это состоит в том, чтобы разместить ClientDataSet и DataSetProvider в удаленном модуле данных сервера. Используйте инспектор объектов, чтобы редактировать свойство FieldDefs ClientDataSet и добавить определения полей. Затем написать обработчик события BeforeGetRecords для DataSetProvider, который получает данные, в этом случае из файла ASCII, и загрузит их в ClientDataSet. DataSetProvider затем получает данные из ClientDataSet и посылает их клиентскому приложению нормальным способом. Рисунок 18 показывает обработчик события BeforeGetRecords.
procedure TPassOther.TextProvBeforeGetRecords(Sender: TObject; var OwnerData:
 OleVariant);
var
 AFile: TextFile;
 FieldVals: TStringList;
 Rec: string;
begin
 FieldVals := TStringList.Create;
 try
 with TextCds do
 begin
 { If the ClientDataSet is active empty it otherwise create it using
 the FildDefs entered at design time. Calling CreateDataSet both
 creates the in memory dataset and opens the ClientDataSet. }
 if Active then
 EmptyDataSet
 else
 CreateDataSet;
 { Open the ASCII file. }
 AssignFile(AFile, OwnerData);
 Reset(AFile);
 { Loop through the ASCII file. Read each record and assign it to the
 CommaText property of the TStringList FieldVals.
 This parses the record and assigns each field to a string in the StringList.
 Insert a new record in the ClientDataSet
 and assign the StringList elements to the fields. }
 while not System.EOF(AFile) do
 begin
 Readln(AFile, Rec);
 FieldVals.Clear;
 FieldVals.CommaText := Rec;
 Insert;
 FieldByName('Name').AsString := FieldVals[0];
 FieldByName('Date').AsDateTime := StrToDate(FieldVals[1]);
 FieldByName('Unit').AsString := FieldVals[2];
 Post;
 end; // while
 System.CloseFile(AFile);
 { Be sure to reposition the ClientDataSet to the first record
 so the DataSetProvider will start with the first
 record when building its data packet to send to the client. }
 First;
 end; // with finally FieldVals.Free;
 end; // try
end;
В начале обработчика BeforeGetRecords создается StringList FieldVals, который используется для просмотра записей из разделенного запятой файла ASCII (csv). Затем это проверяет открытие ClientDataSet, и если он открыт очищает его. Если не открыт вызывает CreateDataSet который, и создает набор данных в памяти, используя FieldDefs, определенный во временя разработки и открывает ClientDataSet. AssignFile и Reset открывают файл ASCII. Обратите внимание, что имя файла в обращении к AssignFile - параметр OwnerData, переданный обработчику события. OwnerData позволяет клиенту передавать любую информацию серверу, устанавливая значение параметра OwnerData в событии BeforeGetRecords ClientDataSet клиентского приложения. OwnerData - Variant, Вы можете передавать любомой типу данных, включая массив вариантов. Это дает Вам возможность передать любое количество значений любого типа.
Цикл while читает запись из текстового файла в строковую переменную Rec, очищает StringList, и устанавливает значение свойства CommaText StringList в Rec. Когда Вы назначаете строку CommaText анализируется наличие любых запятых или пробелов, которые не включены в кавычки, и каждая подстрока записывается в элемент StringList.
Затем, новая запись вставляется в ClientDataset, и значения из StringList присваиваются полям. Новая запись закрепляется. По достижении конца текстового файла CloseFile закрывает файл ASCII. Затем, обращение к First перемещает курсор ClientDataSet к первой записи. Это важно, потому что DataSetProvider начнет с текущей записи, когда будет формировать пакет данных, чтобы послать клиенту. Если Вы оставляете ClientDataSet, позиционированный в последнюю запись, последняя запись единственная, которая будет послана Midas клиенту. В заключение, обращение к методу Free StringList освобождает память.
На клиентских местах все гораздо проще. Когда Вы открываете ClientDataSet в клиентском приложении генерируется событие BeforeGetRecords. Рисунок 19 показывает код для события клиента BeforeGetRecords.
procedure TMainDm.TextCdsBeforeGetRecords(Sender: TObject;
 var OwnerData: OleVariant);
begin
 { Assign the file name to OwnerData which is
 passed to the Midas client automatically. }
 OwnerData := ExtractFilePath(Application.ExeName) + 'text.txt';
end;
Рис.19. Обработчик события BeforeGetRecords
Единственная вещь, которая выполняется здесь - то, что имя текстового файла записывается в параметр OwnerData. OwnerData автоматически отправляется Midas серверу, где, появляется как параметр для BeforeGetRecords события DataSetProvider.
=== Отсылка файла, который не требуется отображать пользователю ===
Использование ClientDataSet удобно для данных, которые Вы хотите отображать на форме. Но предположим, что Вы должны передать файл, который не надо отображать в ClientDataSet от COM сервера к клиенту. Это совершенно, просто, даже если Вы должны послать файл который является слишком большим, чтобы размещаться в память. Закладка File типового приложения содержит кнопку Copy File и компонент Memo. Рисунок 20 - код из обработчика события OnClick этой кнопки. Процедура начинается с объявления константы ArraySize, которая содержит размер массива, используемого для передачи файлов от COM сервера к клиенту. Эта типовая программа отображает блоки чтения данных из сервера в компоненте Memo на форме. В приложении, где Вы передаете большое количество данных и сохраняете их в памяти или пишете в файлу, Вы могли бы использовать намного больший размер массива, например 4КБ или 16КБ, чтобы передать большее количество данных за одно обращения к серверу.
Так как мы хотим помещать данные в компонент Memo, строка байтов, возвращенных из сервера должна быть интерпретирована как строковая переменная, в этом случае S. Обращение к SetLength устанавливает размер S равным размеру массива. Затем компонент DCOMCONNECTION открывается, чтобы установить соединение с сервером, и Memo очищается.
Пересылка файла выполнена тремя пользовательскими методами, добавленными к интерфейсу приложения IAPPSERVER сервера, используя Редактор Библиотеки типов. Первый, OpenFile, берет один параметр - имя файла, который будет перемещен. Цикл while вызывает второй метод IAPPSERVER, GetFileData. GetFileData передает вариант, VDATA, как возвращаемый параметр и размер массива и возвращает число байт фактически прочитанных из файла. Это будет размер массива для каждого блока за исключением последнего, который может содержать меньшее количество байтов, если размер файла - не четный множитель блочного размера. Если число байтов, возвращенных обращением к GetFileData - нуль, конец файла был достигнут, и цикл с условием продолжения покидается.
Следующий шаг должен поместить байты, возвращенные в массиве в строковую переменную, S, и добавлять строку к компоненту Memo. Чтобы обращаться к данным в вариантном массиве, быстрее массив блокирован обращением к VarArrayLock(VDATA), который возвращает указатель на фактический массив данных в варианте. Указатель сохраняется в переменной PDATA, которая объявлена с типом PBYTEARRAY. PBYTEARRAY объявлен в модуле System как указатель на массив типа "байт". Данные перемещаются от массива в строковую переменную вызовом Move(PDATA^, S[1], ByteCount). Процедура Move копирует определенное число байтов из одного участка в памяти в другой. Первый параметр - исходное расположение, второй параметр - адресат, и третий параметр - число копируемых байтов. Обратите внимание, что Move не выполняет никакую проверку ошибок любого вида, так будьте внимательным, чтобы использовать правильные параметры, потому что странные вещи случатся во времени выполнения, если Вы записываете в неправильную область памяти. Кроме того, Move не выполняет никакой контроль соответствия типов. Вы можете перемещать любую комбинацию разрядов в строку или любой другой вид переменной. Если только данные переместились от массива в строку, массив разблокируется, и строка добавляется в Memo.После копирования происходит обращение к третьему методу IAPPSERVER - CloseFile для закрытия файл на сервере.
procedure TMainForm.CopyFileBtnClick(Sender: TObject);
const
 ArraySize = 20;
var
 VData: Variant;
 PData: PByteArray;
 S: string;
 ByteCount: Integer;
begin
 with MainDm.Conn do
 begin
 { Allocate the string variable S to hold the number of bytes
 returned in the variant array. }
 SetLength(S, ArraySize);
 { Connect to the Midas server and empty the memo component. }
 if not Connected then
 Open;
 Memo.Lines.Clear;
 { Call the server's OpenFile method. This creates the TFileStream
 on the server that is used to read the file. The name of the file
 to read is passed as a parameter. }
 AppServer.OpenFile(ExtractFilePath(Application.ExeName) + 'text.txt');
 { Read data from the server until the entire file has been read. }
 while True do
 begin
 {Read a block of data from the server. GetFileData returns the actual
 number of bytes read. The parameter is a variant array of bytes
 passed by reference.}
 VData := Unassigned;
 ByteCount := AppServer.GetFileData(VData, ArraySize);
 { If the number of bytes read is zero the end of the file has been reached. }
 if ByteCount = 0 then
 Break;
 { Lock the variant array and get a pointer to the array values. }
 PData := VarArrayLock(VData);
 try
 { The read that reaches the end of the file may return fewer bytes
 than requested. If so, resize the string variable to hold the
 number of bytes actually read. }
 if ByteCount < ArraySize then
 SetLength(S, ByteCount);
 { Move the data from the variant array to the string variable. }
 Move(PData^, S[1], ByteCount);
 finally
 VarArrayUnlock(VData);
 end; //try
 Memo.Lines.Add(S);
 end; //while
 AppServer.CloseFile;
 end; //with
end;
На стороне сервера методы OpenFile, GetFileData и CloseFile были добавлены к интерфейсу IAPPSERVER, используя редактор библиотеки типа. Рисунок 21 показывает код из удаленного модуля данных для OpenFile метода. OpenFile содержит одиночную строку программы, которая создает объект FileStream для файла, переданного как параметр метода. Файл открыт в режиме чтения и разделен для чтения, но никакая запись не допускается. FileStream назначен к переменной Fs, которая является закрытой переменной удаленного модуля данных.
procedure TPassOther.OpenFile(FileName: OleVariant);
begin
 { Create the TFileStream object in read mode. Allow other applications
 to read the text file but not write to it. }
 Fs := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
end;
Рис. 21 Метод OpenFile
Рисунок 22 показывает метод GetFileData. Этот метод имеет выходной параметр, который является вариантом и возвращает массив байтов, содержащих данные файла. После создания вариантного массива GetFileData блокирует его для быстрого доступа и получает указатель, возвращенный VarArrayLock в локальную переменную PDATA. Затем вызывается метод чтения FileStream, передавая адрес PDATA, указывающий на массив, чтобы сохранить данные и передающий VarArrayHighBound(Данные, 1) + 1, поскольку число байтов всегда равно размеру массива. Число байтов прочитанных фактически возвращается функцией. В заключение, обращение к VarArrayUnlock разблокирует вариантный массив.
function TPassOther.GetFileData(out Data: OleVariant;
 ArraySize: Integer): Integer;
var
 PData: PByteArray;
begin
 Data := VarArrayCreate([0, ArraySize - 1], varByte);
 { Lock the variant array and get a pointer to the array of bytes.
 This makes access to the variant array much faster. }
 PData := VarArrayLock(Data);
 try
 { Read data from the TFileStream. The number of bytes to read is
 the high bound of the variant array plus one (because the array
 is zero based). The number of bytes actually read is
 returned by this function. }
 Result := Fs.Read(PData^, VarArrayHighBound(Data, 1) + 1);
 finally
 VarArrayUnlock(Data);
 end; //try
end;
Рис. 22 Метод GetFileData
Рисунок 23 показывает метод CloseFile, который освобождает объект FileStream и устанавливает переменную Fs к nil. Обработчик события OnDestroy для удаленного модуля данных также освобождает FileStream, если Fs - не nil, на случай если клиентская программа не вызывает CloseFile.
procedure TPassOther.CloseFile;
begin
 if Assigned(Fs) then
 begin
 Fs.Free;
 Fs := nil;
 end;
end;
Рис. 23 Метод CloseFile
Пересылка массивов или других структур, находящихся в памяти
Вы можете также переслать массив или любую другую структуру данных, которая существует в памяти, заполняя в вариантный массив байтов. Рисунок 24 показывает метод выборки GetArray Midas сервера. Этот метод объявляет массив Integer размером 10 элементов. Вариант, VDATA, передан по ссылке клиентским приложением. GetArray вызывает VarArrayCreate, чтобы создать вариантный массив байтов, чей размер равен размеру целочисленного массива, который будет возвращен. Затем вариантный массив блокируется, целочисленный массив перемещается в него, и вариантный массив разблокируется.
procedure TPassOther.GetArray(var VData: OleVariant);
var
 IntArray: array[1..10] of Integer;
 I: Integer;
 PData: PByteArray;
begin
 { Put some numbers in the array. }
 for I := 1 to 10 do
 IntArray[I] := I;
 { Create the variant array of bytes. Set the upper bound to the size
 of the array minus one because the array is zero based. }
 VData := VarArrayCreate([0, SizeOf(IntArray) - 1], varByte);
 { Lock the variant array for faster access then copy the array to the
 variant array and unlock the variant array. }
 PData := VarArrayLock(Vdata);
 try
 Move(IntArray, PData^, SizeOf(IntArray));
 finally
 VarArrayUnlock(VData);
 end; //try
end;
Рис. 24 Метод GetArray
procedure TMainForm.CopyArrayBtnClick(Sender: TObject);
var
 IntArray: array[1..10] of Integer;
 VData: Variant;
 PData: PByteArray;
 I: Integer;
begin
 { Connect to the server application. }
 if not MainDm.Conn.Connected then
 MainDm.Conn.Open;
 { Call the server's GetArray method and pass a variant parameter. }
 MainDm.Conn.AppServer.GetArray(VData);
 { Lock the variant array, copy the data to the
 array and unlock the variant array. }
 PData := VarArrayLock(VData);
 try
 Move(PData^, IntArray, SizeOf(IntArray));
 finally
 VarArrayUnlock(VData);
 end; //try
 { Display the array values in the memo. }
 for I := 1 to 10 do
 ArrayMemo.Lines.Add(IntToStr(IntArray[I]));
end;
Рис.25 Обработчик OnClick
Рисунок 25 показывает OnClick обработчик события для кнопки Copy Array в приложении PassOther. Этот метод соединяется с Midas сервером, вызывая метод Open компонента DcomConnection. Затем вызывает GetArray метод сервера, передавая вариант как параметр. Затем вариант который теперь содержит массив блокируется и данные перемещаются из вариантного массива байтов в целочисленный массив IntArray. В заключение вариантный массив разблокируется, и целые числа отображаются в компоненте Memo на форме.
== Заключение ==
Midas обеспечивает мощный гибкий способ работs с и локальными базами и c удаленными серверами. Это оказывается настолько полезным, чтобы стать краеугольным камнем нового Borland DB Express.
От переводчика:
К сожалению из-за большого объема данной статьи невозможно до конца выправить стилистику и т.д. Если Вы обнаружили какие-либо ошибки, имеете вопросы и предложения, я с радостью приму их по адресу mgoblin@mail.ru.
Author: Bill Todd
Перевод: Михаил Голованов
Source: http://delphiworld.narod.ru
ID: 04326
Category:MIDAS
Category:COM
Category:Статьи