| ||||||||||||||||
![]() | ||||||||||||||||
| ||||||||||||||||
![]() | ||||||||||||||||
| ||||||||||||||||
![]() |
Midas и COM. Советы и Приемы Статья показывает, как писать приложения, использующие несколько модулей, которые связываются через COM и совместно используют Midas сервер. Освещаются вопросы перемещения файлов, массивов и других структур данных c использованием COM. Вы можете не нуждаться в распределенных приложениях, но вы нуждаетесь в Midas
Borland разрабатывал Midas как инструмент для создания многоуровневых распределенных приложений. Но Midas - лучший способ построить любое приложение баз данных, особенно большое приложение, даже, когда Вы не нуждаетесь в распределенной базе данных. Транзакции в настольных базах данных
Если Вы работаете с Paradox или Dbase таблицами, и нуждаетесь в поддержке транзакций, Вы ограничены, потому что единственный уровень изоляции транзакции read uncommited (также грязное чтение). Вдобавок ко всему, невозможно произвести откат при возникновении аварийной ситуации , т.е аварийный отказ может оставлять вашу базу данных в несогласованном состоянии. Однако, если Вы используете ClientDataSet, Вы действительно получаете транзакции и автоматический откат в аварийных ситуациях. Улучшение параллельности работы баз данных Одна из проблема транзакциями в любой базе данных состоит в том, что транзакции, которые являются активными в течение длительного времени, уменьшают способность другого пользователя модифицировать базу данных. Это случается, потому что каждый раз измененная запись должна быть блокирована, и блокировка должна быть задержана, пока транзакция не завершится, чтобы гарантировать, что никакой другой пользователь не может изменять запись. Эта проблема особенно актуальна, если база данных использует блокировку страницы, это делает невозможным для других пользователей изменение строк на блокированной странице. Обеспечение нескольких платформ серверов баз данных Предположим, что Вы пишете приложение для рынка. Вы знаете, что некоторые из ваших потенциальных заказчиков уже выбрали платформу сервера базы данных, так что Вы должны создать версии вашего приложения, для выполнения на Oracle, Microsoft SQL и Interbase. Компоненты, используемые, для работы с базами данных различны. Для Interbase самый лучший выбор Interbase Express, для MS SQL , самый лучший выбор - ADO Express и для Oracle, Вы можете использовать или BDE или ADO Express. Создание модульных приложений Объединение Midas с COM позволяет, создавать большие сложные приложения из множества COM серверов, которые совместно используют общее соединение базы данных. Использование Midas и COM вместе:
Разработка Midas сервера
Midas сервер имеет только одну необычную возможность. Он выполнен как DLL, так что не будет отображать форму или показывать иконку в панели задач. В то время как наличие отображаемой формы сервера может быть допустимым для распределенной системы, где никто обычно не видит экран машины - Midas сервера, это - плохая идея для приложения, где сервер и клиент выполнится на одном PC, потому что пользователь может быть введен в заблуждение дополнительной иконкой и может пробовать закрывать сервер. Решение состоит в том, чтобы выполнить Midas сервер как DLL, так чтобы он не имел никакого интерфейса пользователя. При выполнении Midas сервера в виде DLL также улучшается производительность. Для создания Midas сервера как DLL, выберем меню File | New из меню и затем закладку ActiveX репозитария объектов. Двойным щелчком левой кнопки мыши на иконке ActiveX Library создаст новый проект ActiveX библиотеки. С тех пор как Midas использует COM, чтобы обработать связь между Midas клиентом и Midas сервером, ActiveX библиотека используется, чтобы обеспечить требуемую поддержку COM. ![]() Рис.1. Удаленный модуль данных
Разработка COM Клиента Рисунок 2 показывает главную форму приложения. Она состоит из двух DBGRID и двух DBNAVIGATOR. Верхний DBGRID и DBNAVIGATOR отображают информацию о заказчике и нижний DBGRID и DBNAVIGATOR - таблицу заказов. Рисунок 3 показывает модуль данных для этого приложения. ![]() Рис.2. Главная форма ![]() Рис.3 Модуль данных
![]() Рис. 4. Диалог поиска заказчика
Создание COM сервера
Теперь начинается самое интересное. Следующий наш шаг - создание формы заказов и методов, которые форма просмотра заказчиков будет использовать, чтобы открыть форму заказов, искать заказы заказчика иискать заказ по его номеру. Однако, форма заказов будет расположена в отдельном приложении, которое является сервером Автоматизации, и форма заказчиков вызовает методы формы заказов через интерфейс автоматизации. ![]() Рис.5 Форма заказов ![]() Рис. 6 Модуль данных заказов
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. Программа работы с заказчиками вызывает этот метод для отображения формы заказов. Обратный вызов COM клиенту. Используя вышеописанные методы наше приложение, может вызывать методы на COM сервере, чтобы сформировать заказ и найти заказы по номеру или по принадлежности к заказчику. Однако, COM сервер должен быть способным передавать данные клиенту по двум причинам. Первая, когда пользователь просматривает заказ, он должен иметь возможность отобразить запись заказчика для этого заказа. Другими словами, форма заказов должна уметь сообщить форме заказчика о необходимости найти нужного заказчика и отобразить себя. Вторая проблема состоит в том, что COM сервер показывает форму заказов в режиме modeless. Это означает, что COM клиент не может знать о закрытии COM сервер. Единственое решение состоит в том, что COM сервер должен сообщить 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. 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
![]() Рис.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 как параметр. 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, Вы можете передавать любомой типу данных, включая массив вариантов. Это дает Вам возможность передать любое количество значений любого типа. 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КБ, чтобы передать большее количество данных за одно обращения к серверу. 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.
|
|
![]() | ||||||||||||||||
| ||||||||||||||||
![]() | ||||||||||||||||
|