Delphi World - это проект, являющийся сборником статей и малодокументированных возможностей  по программированию в среде Delphi. Здесь вы найдёте работы по следующим категориям: delphi, delfi, borland, bds, дельфи, делфи, дэльфи, дэлфи, programming, example, программирование, исходные коды, code, исходники, source, sources, сорцы, сорсы, soft, programs, программы, and, how, delphiworld, базы данных, графика, игры, интернет, сети, компоненты, классы, мультимедиа, ос, железо, программа, интерфейс, рабочий стол, синтаксис, технологии, файловая система...
Midas и COM. Советы и Приемы

Автор: Bill Todd
Перевод:  Михаил Голованов

Статья показывает, как писать приложения, использующие несколько модулей, которые связываются через 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 показывает удаленный модуль данных для типового приложения.


Рис.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 - таблицу заказов. Рисунок 3 показывает модуль данных для этого приложения.


Рис.2. Главная форма


Рис.3 Модуль данных

Модуль данных содержит компонент DCOMCONNECTION, два ClientDataSet и два DataSources. Имя DCOMCONNECTION компонента - DemoConn, и св-во ServerName установлено в DemoDllSrvr.DllDemoServer. Свойство RemoteServer CustomerCds установлено в DemoConn, и ProviderName установлено равным CustomerProv. Свойство DataSetField компонента OrdersCds установлено в CustomerCdsOrdersQry, чтобы получить данные из вложенного набора данных. Меню Edit содержит диалог поиска, который отображает диалог, показанный на рисунке 4. Диалог дает возможность выбирать заказчика по номеру или выбирать все записи с определенным состоянием, используя метод FindCustomer в модуле данных CustomerDm. Реализацию данного метода Вы можете посмотреть в коде приложения.


Рис. 4. Диалог поиска заказчика

Меню File на основной форме содержит пункт вызова формы выбора заказов, форма позволяет искать любой заказ по номеру заказчика или номеру заказа. Сетка заказов соединена со всплывающим меню, которое имеет два пункта. Первый, отобразить текущий заказ, открывает форму заказов и показывают текущую запись заказа. Второй, все заказы данного заказчика.

Создание COM сервера

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

Чтобы создать приложение Orders, выберите закладку ActiveX репозитарии и двойным щелчком мыши пункт ActiveX Library. Добавьте форму и модуль данных к приложению. Законченная форма изображена на рисунке 5 и модуль данных на рисунке 6.


Рис.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 *

Код для первых трех методов расположен в модуле OrdersAuto и показан здесь на рисунке 7.


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}

procedure TOrderDm.FindOrder;
{Displays the Find Order dialog. Calls the appropriate find method based on
which edit box on the Find Order dialog has a value.}
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;

procedure TOrderDm.FindByOrderNo(OrderNo: Integer);
{Finds an Order record given its OrderNo.}
begin
  with OrdersCds do
  begin
    Close;
    CommandText := 'SELECT * FROM Orders WHERE ' +
      '(OrderNo = ' + IntToStr(OrderNo) + ')';
    Open;
  end;
end;

Рис. 8. Методы модуля данных OrdersDM


procedure TOrderDm.FindByCustNo(CustNo: Integer);
{Finds all of the Order records for the specified Customer.}
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 клиенту, что необходимо найти и отобразить запись заказчика, чей номер соответствует номеру заказчика текущей записи заказа.

procedure TOrderServer.CloseOrders;
begin
  FEvents.OnCloseOrders;
end;

procedure TOrderServer.FindCustomer;
begin
  FEvents.OnFindCustomer(OrderDm.OrdersCdsCustNo.AsInteger);
end;

Рис.9 Генерация событий

Код обработчиков событий приведен на рисунке 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 сервере.


Рис.12 Диалог импорта библиотеки типов

Киньте 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.


Рис. 15 Главная форма COM клиента


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.

Проект Delphi World © Выпуск 2002 - 2024
Автор проекта: USU Software
Вы можете выкупить этот проект.