Как можно работать с DDE под Delphi, используя вызовы API
Кстати, достаточно легко: следующий пример демонстрирует как можно научить общаться клиентскую программу с программой-сервером. Обе программы полностью созданы на Delphi. В итоге мы имеет 2 проекта, 3 формы и 3 модуля. Для работы с DDE-запросами данный пример использует методы DDE ML API.
Сервер должен начать свою работу перед тем, как клиент будет загружен. Данный пример демонстрирует 3 способа взаимодействия между клиентом и сервером:
Клиент может "пропихивать" (POKE) данные на сервер. Сервер может автоматически передавать данные клиенту, после чего клиент обновляет свой вид на основе результатов, полученных от сервера. Данные сервера изменяются, после чего клиент делает запрос серверу для получения новых данных и обновляет свой вид. Как работает программа. Ниже приведены 8 файлов, сконкатенированных в единое целое. Каждый файл имеет следующую структуру: { *** НАЧАЛО КОДА FILENAME.EXT *** } КОД { *** КОНЕЦ КОДА FILENAME.EXT *** }, поэтому вам остается всего-лишь взять код, расположенный между маркерами { *** }, скопировать в файл с соответствующим именем, и собрать оба проекта в среде Delphi
{ *** НАЧАЛО КОДА DDEMLCLI.DPR *** } program Ddemlcli;
{ *** НАЧАЛО КОДА DDEMLCLU.DFM *** } object Form1: TForm1
Left = 197 Top = 95 Width = 413 Height = 287 HorzScrollBar.Visible = False VertScrollBar.Visible = False Caption = 'Демонстрация DDEML, Клиентское приложение' Font.Color = clWindowText Font.Height = -13 Font.Name = 'System' Font.Style = [] Menu = MainMenu1 PixelsPerInch = 96 OnCreate = FormCreate OnDestroy = FormDestroy OnShow = FormShow TextHeight = 16 object PaintBox1: TPaintBox Left = 0 Top = 0 Width = 405 Height = 241 Align = alClient Color = clWhite ParentColor = False OnPaint = PaintBox1Paint end object MainMenu1: TMainMenu Top = 208 object File1: TMenuItem Caption = '&Файл' object exit1: TMenuItem Caption = 'В&ыход' OnClick = exit1Click end end object DDE1: TMenuItem Caption = '&DDE' object RequestUpdate1: TMenuItem Caption = '&Запрос на обновление' OnClick = RequestUpdate1Click end object AdviseofChanges1: TMenuItem Caption = '&Сообщение об изменениях' OnClick = AdviseofChanges1Click end object N1: TMenuItem Caption = '-' end object PokeSomeData: TMenuItem Caption = '&Пропихивание данных' OnClick = PokeSomeDataClick end end end end { *** КОНЕЦ КОДА DDEMLCLU.DFM *** }
{ *** НАЧАЛО КОДА DDEMLCLU.PAS *** } {***************************************************} { } { Delphi 1.0 DDEML Демонстрационная программа } { Copyright (c) 1996 by Borland International } { } {***************************************************}
{ Это демонстрационное приложение, демонстрирующее использование DDEML API в клиентском приложении. Оно использует серверное приложение DataEntry, которое является частью данной демонстрации, и служит для ввода данных и отображения их на графической панели.
Сначала вы должны запустить приложение-сервер (в DDEMLSRV.PAS), а затем стартовать клиента. Если сервер не запущен, клиент при попытке соединения потерпит неудачу.
Интерфейс сервера определен списком имен (Service, Topic и Items) в отдельном модуле с именем DataEntry (DATAENTR.TPU). Сервер делает Items доступными в формате cf_Text; они преобразовываются и хранятся локально как целые. }
{ Структура данных, представленная в примере } TDataSample = array[1..NumValues] of Integer; TDataString = array[0..20] of Char; { Размер элемента как текста }
{ Локальная функция: Процедура обратного вызова для DDEML }
function CallbackProc(CallType, Fmt: Word; Conv: HConv; hsz1, hsz2: HSZ;
Data: HDDEData; Data1, Data2: Longint): HDDEData; export; begin
CallbackProc := 0; { В противном случае смотрите доказательство }
case CallType of xtyp_Register: begin { Ничего ... Просто возвращаем 0 } end; xtyp_Unregister: begin { Ничего ... Просто возвращаем 0 } end; xtyp_xAct_Complete: begin { Ничего ... Просто возвращаем 0 } end; xtyp_Request, Xtyp_AdvData: begin Form1.Request(Conv); CallbackProc := dde_FAck; end; xtyp_Disconnect: begin ShowMessage('Соединение разорвано!'); Form1.Close; end; end; end;
{ Посылка DDE запроса для получения cf_Text данных с сервера. Запрашиваем данные для всех полей DataSample, и обновляем окно для их отображения. Данные с сервера получаем синхронно, используя DdeClientTransaction.}
procedure TForm1.Request(HConversation: HConv); var
hDdeTemp: HDDEData; DataStr: TDataString; Err, I: Integer; begin
if HConversation <> 0then begin for I := Low(ItemHSz) to High(ItemHSz) do begin hDdeTemp := DdeClientTransaction(nil, 0, HConversation, ItemHSz[I], cf_Text, xtyp_Request, 0, nil); if hDdeTemp <> 0then begin DdeGetData(hDdeTemp, @DataStr, SizeOf(DataStr), 0); Val(DataStr, DataSample[I], Err); end; { if } end; { for } Paintbox1.Refresh; { Обновляем экран } end; { if } end;
procedure TForm1.FormCreate(Sender: TObject); var
I: Integer; { Создаем экземпляр окна DDE-клиента. Создаем окно, используя унаследованный конструктор, инициализируем экземпляр данных.} begin
Inst := 0; { Должен быть нулем для первого вызова DdeInitialize } CallBackPtr := nil; { MakeProcInstance вызывается из SetupWindow } ConvHdl := 0; ServiceHSz := 0; TopicHSz := 0; for I := Low(DataSample) to High(DataSample) do begin ItemHSz[I] := 0; DataSample[I] := 0; end; end;
procedure TForm1.FormDestroy(Sender: TObject); { Уничтожаем экземпляр клиентского окна. Освобождаем дескрипторы DDE строк, и освобождаем экземпляр функции обратного вызова, если она существует. Также, для завершения диалога, вызовите DdeUninitialize. Затем, для завершения работы, вызовите разрушителя предка. } var I: Integer; begin
if ServiceHSz <> 0then DdeFreeStringHandle(Inst, ServiceHSz); if TopicHSz <> 0then DdeFreeStringHandle(Inst, TopicHSz); for I := Low(ItemHSz) to High(ItemHSz) do if ItemHSz[I] <> 0then DdeFreeStringHandle(Inst, ItemHSz[I]);
if Inst <> 0then DdeUninitialize(Inst); { Игнорируем возвращаемое значение }
if CallBackPtr <> nilthen FreeProcInstance(CallBackPtr); end;
procedure TForm1.RequestUpdate1Click(Sender: TObject); begin { Генерируем запрос DDE в ответ на выбор пункта меню DDE | Request.}
Request(ConvHdl); end;
procedure TForm1.FormShow(Sender: TObject); { Завершаем инициализацию окна сервера DDE. Выполняем те действия, которые требует правильное окно. Инициализируем использование DDEML. } var
{ Инициализируем DDE и устанавливаем функцию обратного вызова. Если сервер отсутствует, вызов терпит неудачу. }
if CallBackPtr <> nilthen begin if DdeInitialize(Inst, TCallback(CallBackPtr), AppCmd_ClientOnly, 0) = dmlErr_No_Error then begin ServiceHSz := DdeCreateStringHandle(Inst, DataEntryName, cp_WinAnsi); TopicHSz := DdeCreateStringHandle(Inst, DataTopicName, cp_WinAnsi); InitOK := True; { for I := Low(DataItemNames) to High(DataItemNames) do begin }
for I := 1to NumValues do begin ItemHSz[I] := DdeCreateStringHandle(Inst, DataItemNames[I], cp_WinAnsi); InitOK := InitOK and (ItemHSz[I] <> 0); end;
if (ServiceHSz <> 0) and (TopicHSz <> 0) and InitOK then begin ConvHdl := DdeConnect(Inst, ServiceHSz, TopicHSz, nil); if ConvHdl = 0then begin ShowMessage('Не могу инициализировать диалог!'); Close; end end else begin ShowMessage('Не могу создать строки!'); Close; end end else begin ShowMessage('Не могу осуществить инициализацию!'); Close; end; end; end;
procedure TForm1.AdviseofChanges1Click(Sender: TObject); { Переключаемся на режим DDE Advise с помощью пункта меню DDE | Advise (уведомление). При выборе этого пункта меню все три элемента переключаются на уведомление. } var
I: Integer; TransType: Word; TempResult: Longint; begin
with TMenuITem(Sender) do begin Checked := not Checked; if Checked then TransType := (xtyp_AdvStart or xtypf_AckReq) else TransType := xtyp_AdvStop; end; { with }
for I := Low(ItemHSz) to High(ItemHSz) do if DdeClientTransaction(nil, 0, ConvHdl, ItemHSz[I], cf_Text, TransType, 1000, @TempResult) = 0then ShowMessage('Не могу выполнить транзакцию-уведомление');
if TransType and xtyp_AdvStart <> 0then Request(ConvHdl); end;
procedure TForm1.PokeSomeDataClick(Sender: TObject); { Генерируем DDE-Poke транзакцию в ответ на выбор пункта меню DDE | Poke. Запрашиваем значение у пользователя, которое будем "проталкивать" в DataItem1 в качестве иллюстрации Poke-функции.} var
DataStr: pChar; S: string; begin
S := '0'; if InputQuery('PokeData', 'Задайте проталкиваемую (Poke) величину', S) then begin S := S + #0; DataStr := @S[1]; DdeClientTransaction(DataStr, StrLen(DataStr) + 1, ConvHdl, ItemHSz[1], cf_Text, xtyp_Poke, 1000, nil); Request(ConvHdl); end; end;
procedure TForm1.exit1Click(Sender: TObject); begin
{ Печатаем текст левого поля } TextOut(0, 0, IntToStr(Norm)); TextOut(0, ClipRect.Bottom div2, '0'); TextOut(0, ClipRect.Bottom + Font.Height, IntToStr(-Norm));
TextOut(0, ClipRect.Bottom div2, '0'); TextOut(0, ClipRect.Bottom div2, '0'); TextOut(0, ClipRect.Bottom div2, '0'); { Печатаем текст оси X }
{ Теперь рисуем бары на основе нормализованного значения. Вычисляем ширину баров (чтобы они все вместились в окне) и ширину пробела между ними, который приблизительно равен 20% от их ширины. }
ARect := ClipRect; Wd := (ARect.Right - LMarg) div NumValues; Step := Wd div5; Wd := Wd - Step; with ARect do begin Left := LMarg + (Step div2); Top := ClipRect.Bottom div2; end; { with }
{ Выводим бары и текст для оси X } for i := Low(DataSample) to High(DataSample) do begin with ARect do begin Right := Left + Wd; Bottom := Top - Round((Top - 5) * (DataSample[I] / Norm)); end; { with } { Заполняем бар } Brush.color := clFuchsia; FillRect(ARect); { Выводим текст для горизонтальной оси } Brush.color := clWhite; TextOut(ARect.Left, ClipRect.Bottom div2 - Font.Height, StrPas(DataItemNames[i])); with ARect do Left := Left + Wd + Step; end; { for } end; { with } end; end. { *** КОНЕЦ КОДА DDEMLCLU.PAS *** }
{ *** НАЧАЛО КОДА DDEMLSVR.DPR *** } program Ddemlsvr;
{ *** НАЧАЛО КОДА DDESVRU.DFM *** } object Form1: TForm1
Left = 712 Top = 98 Width = 307 Height = 162 Caption = 'Демонстрация DDEML, Серверное приложение' Color = clWhite Font.Color = clWindowText Font.Height = -13 Font.Name = 'System' Font.Style = [] Menu = MainMenu1 PixelsPerInch = 96 OnCreate = FormCreate OnDestroy = FormDestroy OnShow = FormShow TextHeight = 16 object Label1: TLabel Left = 0 Top = 0 Width = 99 Height = 16 Caption = 'Текущие значения:' end object Label2: TLabel Left = 16 Top = 24 Width = 74 Height = 16 Caption = 'Data Item1:' end object Label3: TLabel Left = 16 Top = 40 Width = 74 Height = 16 Caption = 'Data Item2:' end object Label4: TLabel Left = 16 Top = 56 Width = 74 Height = 16 Caption = 'Data Item3:' end object Label5: TLabel Left = 0 Top = 88 Width = 265 Height = 16 Caption = 'Выбор данных | Ввод данных для изменения значений.' end object Label6: TLabel Left = 96 Top = 24 Width = 8 Height = 16 Caption = '0' end object Label7: TLabel Left = 96 Top = 40 Width = 8 Height = 16 Caption = '0' end object Label8: TLabel Left = 96 Top = 56 Width = 8 Height = 16 Caption = '0' end object MainMenu1: TMainMenu Left = 352 Top = 24 object File1: TMenuItem Caption = '&Файл' object Exit1: TMenuItem Caption = '&Выход' OnClick = Exit1Click end end object Data1: TMenuItem Caption = '&Данные' object EnterData1: TMenuItem Caption = '&Ввод данных' OnClick = EnterData1Click end object Clear1: TMenuItem Caption = '&Очистить' OnClick = Clear1Click end end end end { *** КОНЕЦ КОДА DDESVRU.DFM *** }
{ *** НАЧАЛО КОДА DDESVRU.PAS *** } {***************************************************} { } { Delphi 1.0 DDEML Демонстрационная программа } { Copyright (c) 1996 by Borland International } { } {***************************************************}
{ Данный демонстрационный пример использует библиотеку DDEML на стороне сервера кооперативного приложения. Данный сервер является простым приложением для ввода данных и позволяет оператору осуществлять ввод трех элементов данных, которые становятся доступными через DDE "заинтересованным" клиентам.
Данный сервер предоставляет свои услуги (сервисы) для данных со следующими именами:
В-принципе, в качестве сервисов могли бы быть определены и другие темы. Полезными темами, на наш взгляд, могут быть исторические даты, информация о сэмплах и пр..
Вы должны запустить этот сервер ПЕРЕД тем как запустите клиента (DDEMLCLI.PAS), в противном случае клиент не сможет установить связь.
Интерфейс для этого сервера определен как список имен (Service, Topic и Items) в отдельном модуле с именем DataEntry (DATAENTR.TPU). Сервер делает Items доступными в формате cf_Text; они преобразовываются и хранятся у клиента локально как целые. }
NumValues = 3; DataItemNames: array[1..NumValues] of PChar = ('DataItem1', 'DataItem2', 'DataItem3'); type
TDataString = array[0..20] of Char; { Размер элемента как текста } TDataSample = array[1..NumValues] of Integer;
{type { Структура данных, составляющих образец } { TDataSample = array [1..NumValues] of Integer; { TDataString = array [0..20] of Char; { Размер элемента как текста }