Отправка сообщений делфи. Пользовательские сообщения. Полный исходный код модуля

Посылка сообщений

Так же как система Windows посылает свои сообщения различным окнам, в самом приложении также может появиться необходимость обмена сообщениями между его собственными окнами и элементами управления. Для посылки сообщений существует несколько способов: метод PerForm() (работающий независимо от API Windows), а также функции API Win32 SendMessage() и PostMessage().

Метод PerForm(), которым обладают все потомки класса TControl:

function TControl.Perform(Msg: Cardinal; WParam, LParam: Longint): Longint;

Чтобы послать сообщение форме или элементу управления, применяется следующий синтаксис:

RetVal:= ControlName.PerForm(MessageID, wParam, lParam);

При вызове PerForm() управление вызывающей программе не возвратится, пока сообщение не будет обработано. Этот метод передает сообщение, минуя систему передачи сообщений API Windows.

Функции API SendMessage() и PostMessage(), объявленные в модуле Windows следующим образом:

function SendMessage(hWnd: HWND; Msg: UINT; wParam: WPARAM;

lParam: LPARAM): LRESULT; stdcall;

function PostMessage(hWnd: HWND; Msg: UINT;

wParam: WPARAM; lParam: LPARAM): BOOL; stdcall;

hWnd – дескриптор окна получателя сообщения; Msg – идентификатор сообщения; wParam и lParam – дополнительные данные.

Функция SendMessage() подобно методу PerForm() посылает сообщение непосредственно процедуре окна и ожидает его обработки, а функция PostMessage() помещает сообщение в очередь сообщений и возвращает управление вызвавшей ее программе, не дожидаясь результатов обработки.

Функция SendMessage() возвращает значение, полученное в результате обработки сообщения. Функция PostMessage() – возвращает значение, которое показывает, удалось ли поместить сообщение в очередь сообщений.

Пользовательские сообщения

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

Пример пользовательского сообщения:

TestMsg = WM_USER + 100; // идентификатор сообщения

TForm1 = class(TForm)

// метод обработки сообщения:

procedure MyMessage(var Msg: TMessage); message TestMsg;

procedure TForm1.MyMessage(var Msg: TMessage);

ShowMessage("Работает сообщение TestMsg");

Msg.Result:= 1; // возвращаемый результат

Примеры посылки сообщения форме:

if Form1.PerForm(TestMsg, 0, 0) = 1 then

if SendMessage(Form1.Handle, TestMsg, 0, 0) = 1 then

ShowMessage("Сообщение успешно обработано");

if PostMessage(Form1.Handle, TestMsg, 0, 0) then

ShowMessage("Сообщение помещено в очередь сообщений");

События Delphi

Событие – это то, что происходит в процессе работы программы. С точки зрения языка Delphi, событие – это свойство процедурного типа, и его значением является указатель на некоторый метод. Присвоить такому свойству значение означает указать адрес метода, который будет выполняться в момент наступления события. Такие методы называются обработчиками событий.

Использование событий позволяет к существующему классу добавить новую функциональность без создания класса-потомка.

Свойства событий стараются начинать со слова "On", за которым следует имя события.

Взаимосвязь сообщений и событий

Delphi представляет собой интерфейс для взаимодействия с сообщениями Windows, по крайней мере – с некоторой их частью. Многие события компонентов библиотеки VCL непосредственно связаны с сообщениями Windows типа WM_XXX.

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

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

Заставить приложение послать сообщение самому себе очень просто - достаточно либо воспользоваться функциями интерфейса 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;

где то так

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. А потом, когда нужно делать цикл по подключенным клиентам, просто вычитываем его. Конечно, если пользователей тысячи, каждый раз просматривать всех пользователей будет накладно. Но как делать это более оптимально - тема другой большой статьи.

Плавно подходим мы к отправке sms -сообщений через sms-шлюз операторов сотовой связи. В данной статье мы рассмотрим как вообще можно отправить сообщение (email -сообщение) средствами Delphi . Отсылать email -сообщения мы будем через TidSMTP . То есть получается, что, нам надо знать адресс smtp -сервера, с которого мы будем отсылать сообщение. Обычно, сервисы, которые предоставляют услуги почты, перед их адрессом приписывается smtp и получается адресс smtp -сервера. В итоге возьмем rambler.ru . Соответственно, подставим smtp и получим smtp.mail.ru — это и есть адресс нашего smtp-сервера, порт почти везде одинаков данных серверов и он равняется 25 . Это нам все пригодится при подключении к нашей серверу. Далее нам необходим наш пароль и логин с rambler.ru для подключения к нашей почте, с которой мы будем отсылать сообщения. Теперь приступим на форму установим следующие компоненты

  • TidSMTP
  • TidAntiFreeze
  • TButton

Компоненты на форме, значит осталось как ничего подключиться к нашему серверу и отправить сообщения. Для подключения к нашему серверу на событие формы OnCreate напишем следующий код

procedure TForm1. FormCreate (Sender: TObject ) ; begin try IdSMTP1. AuthenticationType : = atLogin; IdSMTP1. Host : = "smtp.rambler.ru" ; IdSMTP1. Port : = 25 ; IdSMTP1. Username : = "[email protected]" ; IdSMTP1. Password : = "password" ; IdSMTP1. Connect ; except on e: Exception do end ; end ;

Строчка IdSMTP1.AuthenticationType:=atLogin; указывает на то, что мы авторитизируемся на нашем сервере. Это у нас и есть подключение к нашему smtp -серверу, с помощью Connect мы указываем, что подключаемся. Далее нам необходимо отправить сообщение, для этого существует специальный тип данных TidMessage , который определяет полностью формат нашего отправляемого письма. Чтобы использовать данный тип данных, необходимо в Uses подключить модуль idmessage . Для начала его необходимо создать и затем заполнить по определенному шаблону и после чего отослать на нужный нам адресс. Теперь на событие TButton - Onclick напишем заполнение нашего сообщения и отправку его.

procedure TForm1. Button1Click (Sender: TObject ) ; var msg: TIdMessage; begin try msg: = TIdMessage. Create (nil ) ; msg. Body . Add ("test mail" ) ; msg. Subject : = "header message" ; msg. From . Address : = "[email protected]" ; msg. From . Name : = "Andrey" ; msg. Recipients . EMailAddresses : = "[email protected]" ; msg. IsEncoded : = True ; IdSMTP1. Send (msg) ; msg. Free ; IdSMTP1. Disconnect ; except on e: Exception do begin msg. Free ; IdSMTP1. Disconnect ; end ; end ; end ;

Теперь можете залезть на эмаил , куда вы отправляли письмо и убедиться, что оно пришло, если конечно ошибок при отправке никаких не было.Как видите Body.Add — задает текст сообщения, Subjects - заголовок сообщения, From.Adress — адресс отправителя, From.Name — имя отправителя, Recipients.EMailAddresses - определяет адрессат получателя (то есть кому отправляем мы email ). В конце мы просто отключаемся от нашего smtp -сервера при помощи метода Disconnect .

Зачастую, программы на 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);