Отправка сообщений делфи. Delphi

Отправка сообщений делфи. Delphi

Последовательность обработки сообщений в Delphi
Все классы Delphi имеют встроенный механизм обработки сообщений, называемый обработчики сообщений. Класс получает какое-либо сообщение и вызывает один из набора определенных методов в зависимости от полученного сообщения. Если соответствующий метод не определен, то вызывается заданный по умолчанию обработчик. Более подробно этот механизм работает следующим образом.

После того, как сообщение получено, система сообщений VCL выполняет большую предварительную работу по его обработке.

Как уже отмечалось выше, первоначально сообщение обрабатывается методом TApplication.ProcessMessage, который выбирает его из очереди в основном цикле сообщений. При этом проверяет содержимое поля FOnMessage (фактически проверяет наличие’обработчика у события OnMessage) и, если оно не пусто, то вызывает обработчик этого события, а если поле пусто (Nil), то вызывает функцию API DispatchMessage(Msg). При отправке сообщения этого не происходит.

Если обработчик события OnMessage не определен, то для обработки полученного сообщения вызывается функция API DispatchMessage, которая передает сообщение главной процедуре окна.

Рассмотрим цикл обработки сообщения после поступления его в главное окно компонента. Последовательность обработки сообщения представлена на следующем рисунке:

Видно, что сообщение передается в MainWndProc, далее в WndProc, далее в Dispatch, далее в DefaultHandler.

В Delphi предусмотрен главный не виртуальный метод MainWndProc (Var Message: TMessage) для окна каждого компонента. Он содержит блок обработки особых ситуаций, передавая структуру сообщения от Windows до виртуального метода, определенного в свойстве WindowProc. При этом этот метод обрабатывает любые исключения, которые происходят в течение обработки сообщения, вызывая метод HandleException приложения. Начиная с этого места можно обеспечить специальную обработку сообщения, если это требуется логикой работы вашей программы. Обычно на этом этапе обработку изменяют, чтобы не дать состояться стандартной обработке VCL.

По умолчанию значение свойства WindowProc объекта инициализируется адресом виртуального метода WndProc. Далее, если нет зарегистрированных перехватчиков сообщения типа TWindowHook, WndProc вызывает виртуальный метод TObject.Dispatch, который, используя поле Msg структуры поступившего сообщения, определяет, находится ли это сообщение в списке обработчиков сообщения для данного объекта. Если объект не обрабатывает сообщение, исследуется список обработчиков сообщений предков. Если такой метод, в конце концов, будет найден, то он вызывается, в противном случае вызывается виртуальный метод DefaultHandler.

Наконец сообщение достигает соответствующей процедуры его обработки, где производится предусмотренная для него обработка. С помощью ключевого слова Inherited оно далее отправляется для обработки в предках. После этого сообщение также попадает в метод DefaultHandler, который производит завершающие действия по обработке сообщений и передает его процедуре DefWindowProc (DefMDIProc) для стандартной обработки Windows.

Обработка сообщений компонентами Delphi
Таким образом, краткое описание последовательности обработки сообщения выглядит следующим образом. Все сообщения первоначально проходят через метод, адрес которого указан в свойстве WindowProc. По умолчанию это метод WndProc. После чего они разделяются и рассылаются по своим методам сообщений. В конце они вновь сходятся в методе DefaultHandler, если не были обработаны ранее или в обработчиках вызывается унаследованный обработчик (Inherited). Следовательно, у компонентов Delphi есть следующие возможности для обработки сообщений:
а) До того, как какой-нибудь обработчик сообщения увидит сообщение. В этом случае требуется либо замена адреса метода в свойстве WindowProc, либо замещение метода TControl.WndProc.
Свойство WindowProc объявлено следующим образом:

Туре TWndMethod=Procedure (Var Message: TMessage) Of Object ;
Property WindowProc: TWndMethod;

Фактически, используя свойство WindowProc, можно создать метод типа TWndMethod и временно заменить исходный метод на созданный, однако, поскольку адрес метода в свойстве WindowProc не сохраняется, то необходимо предварительно сохранять адрес исходного метода WndProc, чтобы его можно было восстановить позднее.

OldWndProc: TWndMethod;
procedure NewWndProc(var Message: TMessage);
procedure TForm1.NewWndProc(var Message: TMessage);
var Ch: char;
begin
if message.Msg= WM_MOUSEMOVE then begin
Edit1.Text:=’x=’+inttostr(message.LParamLo)+’, y=’+inttostr(message.LParamHi);
end
else WndProc(Message);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
OldWndProc:=WindowProc;
end;

procedure TForm1.CheckBox1Click(Sender: TObject);
begin
If CheckBox1.Checked then WindowProc:= NewWndProc
else WindowProc:= OldWndProc;
end;

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

В классе TForml объявим этот метод с целью его переопределения и представим реализацию переопределенного метода сообщения:

Туре TForml=Class(TForm)
… // Все иные необходимые объявления
Protected
Procedure WMPaint(Var Msg: TWMPaint); Message WM_PAINT; End;
Procedure TForml.WMPaint(Var Msg: TWMPaint); Begin
If CheckBox1.Checked Then ShowMessage(‘О6pa6oтчик сообщения!’);
Inherited;
End;

При переопределении конкретных обработчиков сообщений всегда целесообразно вызывать Inherited для выполнения базовой обработки сообщения, которое необходимо Windows.

в) После того, как каждый из соответствующих сообщению методов увидит его.

В этом случае необходимо замещать метод DefaultHandler.

procedure DefaultHandler(var Message); override;
procedure TForm1.DefaultHandler(var Message);
var i:integer;
begin
if Cardinal(Message)=WM_defh then
for i:= 0 to 10 do begin
beep;
sleep(100);
end
else
inherited;
end;

procedure TForm1.Button5Click(Sender: TObject);
begin
SendMessage(Handle,WM_defh,0,0);
end;

Связь между сообщениями и событиями
Многие события VCL Delphi непосредственно связаны с сообщениями Windows. В справочной системе Delphi эти соответствия перечислены. Представлены они в табл.1.

Таблица 1

Событие VCL Сообщение Windows Событие VCL Сообщение Windows
OnActivate WM_ACTIVATE OnKeyPress WM_CHAR
OnClick WM_LBUTTONDOWN OnKeyUp WM_KEYUP
OnCreate WM_CREATE OnPaint WM_PAINT
OnDblClick WM_LBUTTONDBLCLK OnResize WM_SIZE
OnKeyDown WM_KEYDOWN OnTimer WM_TIMER

He стоит создавать обработчики сообщений, если для него имеется предопределенное событие. В подобных случаях целесообразно использовать обработку события, поскольку на нее накладывается меньше ограничений.

При разработке приложений может возникнуть ситуация, при которой приложению потребуется послать сообщение либо самому себе, либо другому пользовательскому приложению. У некоторых предыдущее утверждение вызовет недоумение: зачем при ложению посылать сообщение самому себе, если можно просто вызвать соответствую щую процедуру? Это хороший вопрос, и на него существует несколько ответов. Во первых, использование сообщений представляет собой механизм поддержки реального полиморфизма, поскольку не требует наличия каких либо знаний о типе объекта полу чающего сообщение. Таким образом, технология работы с сообщениями имеет ту же мощь, что и механизм виртуальных методов, но обладает существенно большей гибко стью. Во вторых, сообщения допускают необязательную обработку - если объект получатель не обработает поступившее сообщение, то ничего страшного не произой дет. И, в третьих, сообщения позволяют осуществлять широковещательное обращение к нескольким получателям и организовывать параллельное прослушивание, что доста точно трудно реализовать с помощью механизма вызова процедур.

Использование сообщений внутри приложения

Заставить приложение послать сообщение самому себе очень просто - достаточно либо воспользоваться функциями интерфейса API Win32 SendMessage() или Post- Message(), либо методом Perform(). Сообщение должно обладать идентификато ром в диапазоне от WM_USER+100 до $7FFFF (этот диапазон Windows резервирует для сообщений пользователя). Например:const

SX_MYMESSAGE = WM_USER + 100;

SomeForm.Perform(SX_MYMESSAGE, 0, 0);

SendMessage(SomeForm.Handle, SX_MYMESSAGE, 0, 0);

PostMessage(SomeForm.Handle, SX_MYMESSAGE, 0, 0);

Затем, для перехвата этого сообщения, создайте обычную процедуру обработчик, выполняющую в форме необходимые действия:

TForm1 = class(TForm)

procedure SXMyMessage(var Msg: TMessage); message SX_MYMESSAGE;

procedure TForm1.SXMyMessage(var Msg: TMessage);

MessageDlg(‘She turned me into a newt!’,

mtInformation, , 0);

Как видно из примера, различия в обработке собственного сообщения и стандарт ного сообщения Windows невелики. Они заключаются в использовании идентифика торов в диапазоне от WM_USER+100 и выше, а также в присвоении каждому сообще нию имени, которое каким то образом будет отражать его смысл.

Никогда не посылайте сообщений, значение WM_USER которых превосходит $7FFF, ес- ли не уверены абсолютно точно, что получатель способен правильно обработать это сообщение. Поскольку каждое окно может независимо выбирать используемые им значения, весьма вероятно появление трудноуловимых ошибок, если только заранее не составить таблицы идентификаторов сообщений, с которыми будут работать все отправители и получатели сообщений.

Обмен сообщениями между приложениями

При необходимости организовать обмен сообщениями между двумя или более приложениями в них следует использовать функцию API RegisterWindowMessage(). Этот метод гарантирует, что для заданного типа сообщений каждое приложение бу дет использовать один и тот же номер сообщения (message number).

Функция RegisterWindowMessage() принимает в качестве параметра строку с за

вершающим нулевым символом и возвращает для нового сообщения идентификатор вдиапазоне $C000 – $FFFF. Это означает, что вызова данной функции с одной и той же строкой в качестве параметра в любом приложении будет достаточно, чтобы га рантировать одинаковые номера сообщений во всех приложениях, принимающих участие в обмене. Еще одно преимущество такой функции состоит в том, что система гарантирует уникальность идентификатора, назначенного любой заданной строке. Это позволяет посылать широковещательные сообщения всем существующим в сис теме окнам, не опасаясь нежелательных побочных эффектов. Недостатком данного метода является некоторое усложнение обработки подобных сообщений. Суть заклю чается в том, что идентификатор сообщения становится известным только при рабо те приложения, поэтому использование стандартных процедур обработки сообщений оказывается невозможным. Для работы с подобными сообщениями потребуется пе реопределить стандартные методы WndProc() или DefaultHandler() элементов управления либо соответствующие процедуры класса окна.

НА ЗАМЕТКУ

Число, возвращаемое функцией RegisterWindowMessage(), создается динамически и может принимать различные значения в разных сессиях Windows, а значит, не может быть определено до момента выполнения программы.

Широковещательные сообщения

Любой класс, производный от класса TWinControl, позволяет с помощью метода Broadcast() послать широковещательное сообщение (broadcast message) любому элементу управления, владельцем которого он является. Эта технология используется в тех случа ях, когда требуется послать одно и то же сообщение группе компонентов. Например, чтобы послать пользовательское сообщение по имени um_Foo всем элементам управле ния, принадлежащим объекту Panel1, можно воспользоваться следующим кодом:

Message:= UM_FOO;

Разработать программу, которая будет предоставлять интерфейс для использования стандартной для Win2000/XP команды передачи сообщений net send. Дать возможность указать пользователю адрес получателя, текст сообщения и количество отправляемых сообщений. Также предусмотреть возможность установки блокировки на получение сообщений от других компьютеров.

Разработка формы

Создайте новый проект Delphi. Измените заголовок формы (свойство Caption) на Net Sender. Разместите вдоль левого края формы один над другим три компонента Label категории Standard и присвойте их свойству Caption значения IP-адрес:, Сообщение: И Количество:.

Рядом с каждой из меток разместите по компоненту Edit категории Standard . Самый верхний назовите ip (свойство Name), а свойству Text присвойте значение 192.168.0.1.; среднее поле назовите txt, а свойству Text присвойте какой-либо текст сообщения по умолчанию; самое нижнее поле назовите how, а свойству Text присвойте значение 1.

Под перечисленными компонентами разместите компонент Checkbox категории Standard . Присвойте ему имя secure, свойству Caption присвойте значение Отключить прием сообщений, а свойству Checked - значение True.

В самом низу формы разместите кнопку (компонент Button категории Standard ), присвоив ее свойству Caption значение Send. Также нам понадобится таймер (компонент Timer категории System ), для которого свойству Interval следует присвоить значение 10.

Полученная форма должна соответствовать рис. 15.1.

Рис. 15.1. Форма для программы отправки сообщений в локальной сети

Разработка программного кода

Прежде всего напишем собственную процедуру bomb, которая будет считывать все настройки и отправлять сообщение. Объявите эту процедуру как закрытый член класса формы:

Также нам понадобится глобальная переменная i типа integer:

Теперь создадим реализацию процедуры bomb в разделе implementation:

procedure TForm1.bomb() ;
if how.Text= "" then how.Text:= "1";
if ip.Text = "" then ip.Text:= "127.0.0.1"; {если ip-адрес не указан, то отправляем на локальный компьютер}
WinExec(PChar("net send " + ip.Text + """ + txt.Text + """), 0); //отправка сообщения

В этой процедуре выполняется проверка: все ли необходимые поля заполнены. Если нет текста сообщения, то устанавливаем знак "!"; если не указан IP-адрес, то отправляем сообщение на локальный компьютер с адресом 127.0.0.1; если не указано количество сообщений, то отправляем одно сообщение. Сообщения отправляются с помощью стандартной команды net send, которая имеет следующий синтаксис:

net send ip-адрес сообщение.

Теперь обработаем событие таймера OnTimer:

h: HWND; //хранит идентификатор окна
if not secure.Checked then //если флажок не установлен
Timer1.Enabled:= False; //отключаем мониторинг
if secure.Checked then //если флажок установлен
//ищем окна с сообщениями
h:= FindWindow(nil, "Служба сообщений "); //закрываем все найденные окна
if h <>

Если установлен флажок Отключить прием сообщений, то мы начинаем мониторинг окон, заголовок которых говорит о том, что это - сообщение, и закрываем все найденные окна. Если флажок не установлен, то мониторинг отключается.

Для того чтобы можно было переключаться между этими двумя режимами, необходимо создать обработчик события secure.OnClick:

if secure.Checked then //если флажок установлен…
Timer1.Enabled:= True; //…включаем мониторинг

При нажатии кнопки Send мы будем просто вызывать процедуру bomb:

Для того чтобы облегчить пользователю жизнь, сделаем так, чтобы отправка сообщения осуществлялась также по нажатии клавиши в любом текстовом поле ввода. Для этого необходимо создать обработчик события OnKeyPress для каждого из полей. Код этого обработчика для поля ip, который затем можно назначить полям txt и how:

if key = #13 then //если нажата клавиша
bomb; //отправка сообщения

Полный исходный код модуля

Полный код модуля программы отправки сообщений по локальной сети представлен в листинге 15.1.

Листинг 15.1. Модуль программы отправки сообщений по локальной сети

Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls;

procedure Timer1Timer(Sender: TObject);
procedure secureClick(Sender: TObject);
procedure ipKeyPress(Sender: TObject; var Key: Char);
procedure txtKeyPress(Sender: TObject; var Key: Char);
procedure howKeyPress(Sender: TObject; var Key: Char);
procedure Button1Click(Sender: TObject);


//проверяем, не пустое ли текстовое сообщение
if txt.Text = "" then txt.Text:= "!";
//если количество не указано, то отправляем одно сообщение
if how.Text= "" then how.Text:= "1";
if ip.Text = "" then ip.Text:= "127.0.0.1"; {если ip-адрес не указан, то отправляем на локальный компьютер}
//отправляем указанное количество сообщений
for i:=1 to StrToInt(how.Text) do
WinExec(PChar("net send " + ip.Text + """ + txt.Text + """), 0); //отправка сообщения

procedure TForm1.Timer1Timer(Sender: TObject);
h: HWND; //хранит идентификатор окна
if not secure.Checked then //если флажок не установлен
Timer1.Enabled:= False; //отключаем мониторинг
if secure.Checked then //если флажок установлен
//ищем окна с сообщениями
h:= FindWindow(nil, "Служба сообщений "); //закрываем все найденные окна
if h <> 0 then PostMessage(h, WM_QUIT, 0, 0);

procedure TForm1.secureClick(Sender: TObject);
if secure.Checked then //если флажок установлен…
Timer1.Enabled:= True; //…включаем мониторинг

procedure TForm1.ipKeyPress(Sender: TObject; var Key: Char);
if key = #13 then //если нажата клавиша
bomb; //отправка сообщения

procedure TForm1.Button1Click(Sender: TObject);

⊚ Все файлы проекта и исполняемый файл рассмотренной программы находятся на прилагаемом к книге компакт-диске в папке Chapter 15.

Зачастую, программы на delphi используют средствами email. В этой статье будет полностью рассказано, как отправляется ваше письмо другому пользователю. при помощи delphi. При этом, будем использовать только стандартные компоненты делфи.

Для начала создадим новый проект и назовём его \»Отправка писем средствами delphi\». Затем, на форму необходимо перенести несколько компонентов 1x Memo, 3x Edit, 2x Botton, а также необходимо перенести IdSMTP, IdAntiFreeze, IdMessage. Далее, на событие onclick любой кнопки пишем:

//выбираем SMTP сервер. В данный момент стоит yandex. IdSMTP1.Host:= "smtp.yandex.ru"; //ваш логин (для некоторых необходимо писать с доменом). IdSMTP1.Username:= "[email protected]"; //пароль от почты. IdSMTP1.Password:= "qwerty123"; //порт, рекомендуем использовать 587. IdSMTP1.Port:=587; //в Edit2 будет вписываться тема письма. IdMessage1.Subject:= Edit2.Text; //в Edit1 будет адрес получателя. IdMessage1.Recipients.EMailAddresses:= Edit1.Text; //ваш email с которого идёт отправка. IdMessage1.From.Address:= "[email protected]"; //в memo1 будет текст который вы ходите послать. IdMessage1.Body.Text:= memo1.Text ; //в Edit3 будет ваша электронная подпись (Имя). IdMessage1.From.Name:= Edit3.Text; //соединяемся IdSMTP1.connect; //отправляем IdSMTP1.Send(IdMessage1); //отсоединяемся IdSMTP1.Disconnect;

Если IdMessage отображает знаки вопроса

Данный баг связан с тем, что Вы вписываете русские буквы, а компонент memo не может их грамотно прочитать, для этого необходимо указать кодировку Utf8.

// устанавливаем кодировку IdMessage1.Charset:="UTF-8"; // переводим текст в нужную кодировку IdMessage1.Body.Text:=UTF8Encode(memo1.text);

где то так

IdTCPClient1.Host:= "127.0.0.1"; IdTCPClient1.Connect;// подключились IdTCPClient1.Socket.WriteLn("command"); // отправили команду command и перевод строки //Ожидаем ответ и закрываем соединение txtResults.Lines.Append(IdTCPClient1.Socket.ReadLn); IdTCPClient1.Disconnect;

в данном случае команда - это просто текст с переводом строки. Это сильно упрощает прием команды с другой стороны (просто ReadLn). В общем же случае нужно придумывать (или использовать готовый) протокол.

выше это был клиент. А теперь сервер. С сервером все немного сложнее. Понятное дело, что для сервера нормально обслуживать не одного клиента, многих. И для этого есть несколько "схем".

    Классическая - один клиент - один поток. Схема проста в кодировании, интуитивно понятна. Хорошо распаралеливается по ядрам. Недостаток - обычно очень сложно создать много потоков, а это ограничивает кол-во клиентов. Для 32битных программ верхний предел где то в районе 1500 (полторы тысячи) потоков на процесс. Но в этом случае накладные расходы на их переключение могут "скушать" весь проц. Именно эта схема используется в indy.

    Вторая классическая - все клиенты на один поток. Эта схема часто более сложна в кодировании, но при правильном подходе позволяет держать 20-30к "медленных пользователей" практически не нагружая ядро. Сильный плюс этой схемы - можно обойтись без мютексов и других примитивов синхронизации. Эту схему использует NodeJS и стандартные классы для работы с сетью в Qt.

    Смешанная. В этом случае создается несколько потоков, каждый с которых обслуживает какое-то кол-во клиентов. Самая сложная в кодировании, но позволяет по максимуму использовать ресурсы железа.

Как это сделано в Indy. Indy tcp сервер для каждого подключения создает отдельный поток (TThread) и дальнейшая работа с клиентом идет в нем. Indy красиво это прячет, оставляя для пользователя только необходимость реализовать метод IdTCPServer.onExecute . Но, как я сказал выше, этот метод запускается в отдельном треде и он у каждого клиента свой личный. Это значит следующее:

  • в этом методе можно позвать sleep и только один клиент будет ждать. Все остальные будут работать (а вот если в обработчике нажатия кнопки позвать sleep, то результат известен)
    • обращаться к глобальным переменным лучше только через примитивы синхронизации.
    • обращаться к элементам gui нужно аккуратно и правильно. Напрямую лучше не делать (некоторые компоненты разрешают обращаться к ним с других потоков, но нужно внимательно читать доки).
    • обращаться к другим клиентам нужно через блокировку (потому что если два потока захотят писать одному и тому же пользователю - ничего хорошего с этого не выйдет).

Рассмотрим очень простой пример. На любой запрос клиента отвечаем тем же и закрываем соединение (такой себе echo сервер).

Procedure TForm1.IdTCPServer1Execute(AContext: TIdContext); var strText: String; begin //Принимаем от клиента строку strText:= AContext.Connection.Socket.ReadLn; //Отвечаем AContext.Connection.Socket.WriteLn(strText); //Закрываем соединение с пользователем AContext.Connection.Disconnect; end;

AContext это специальный объект, который содержит всю необходимую информацию о клиенте. Сам idTcpServer содержит список этих контекстов и к нему можно обратиться. Рассмотрим более сложный бродкаст. То есть, разослать одно сообщение всем

Var Clients: TList; i: integer; begin // защита от дурака:) if not Assigned(IdTCPServer1.Contexts) then exit; // получим список клиентов, и заблокируем его Clients:=IdTCPServer1.Contexts.LockList; try for i:= 0 to Clients.Count-1 do try //LBuffer имеет тип TBytes и содержит подготовленные данные для отправки // но можно и WriteLn использовать. TIdContext(Clients[i]).Connection.IOHandler.Write(LBuffer); except // тут нужно добавить логику. Клиент может отключиться в процессе end; finally // важно! список нужно разблокировать, иначе другие методы не смогут пройти дальше Contexts.LockList IdTCPServer1.Contexts.UnlockList; end; end;

инди содержит BytesToString() и ToBytes() для предобразования String и TIdBytes друг в дружку.

Список блокируется, что бы другие не могли его модифицировать. Иначе сам цикл сильно усложняется. И главное, не забывать разблокировать!

Остался последний вопрос. Как отправить сообщение какому то определенному клиенту. Для этого нужно научиться идентифицировать соединение. Это можно сделать несколькими способами - посмотреть в айпи/порт. Но есть лучше. У IdContext (точнее у его предка idTask) есть свойство Data типа TObject. В него можно записать свой объект и хранить там все нужные данные. Типичный пример использования будет следующий. Когда клиент только подключился - это поле пустое. Когда он прошел проверку имени-пароля - создаем объект (свой), сохраняем имя туда и прописываем в свойство Data. А потом, когда нужно делать цикл по подключенным клиентам, просто вычитываем его. Конечно, если пользователей тысячи, каждый раз просматривать всех пользователей будет накладно. Но как делать это более оптимально - тема другой большой статьи.