Sending delphi messages. Delphi

Sending delphi messages. Delphi

Message Processing Sequence in Delphi
All Delphi classes have a built-in message handling mechanism called message handlers. The class receives a message and calls one of a set of defined methods depending on the message received. If the corresponding method is not defined, then the default handler is called. In more detail, this mechanism works as follows.

After a message is received, the VCL messaging system does a lot of preliminary work to process it.

As noted above, the message is initially processed by the TApplication.ProcessMessage method, which selects it from the queue in the main message loop. At the same time, it checks the contents of the FOnMessage field (actually checks for the presence of a handler for the OnMessage event) and, if it is not empty, then calls the handler for this event, and if the field is empty (Nil), then calls the DispatchMessage(Msg) API function. This does not happen when sending a message.

If the OnMessage event handler is not defined, then the DispatchMessage API function is called to process the received message, which passes the message to the window's main procedure.

Let's consider the message processing loop after it arrives in the component's main window. The message processing sequence is shown in the following figure:

It can be seen that the message is passed to MainWndProc, then to WndProc, then to Dispatch, then to DefaultHandler.

Delphi has a main non-virtual method MainWndProc(Var Message: TMessage) for the window of each component. It contains an exception handling block, passing the message structure from Windows to the virtual method defined in the WindowProc property. However, this method handles any exceptions that occur during message processing by calling the application's HandleException method. Starting from this place, you can provide special treatment messages, if required by the logic of your program. Typically, at this stage, the processing is changed to prevent the standard VCL processing from taking place.

By default, the value of the object's WindowProc property is initialized to the address of the WndProc virtual method. Further, if there are no registered TWindowHook message hooks, WndProc calls the TObject.Dispatch virtual method, which, using the Msg field of the incoming message structure, determines whether this message is in the list of message handlers for this object. If the object does not handle the message, the list of ancestor message handlers is examined. If such a method is eventually found, then it is called; otherwise, the DefaultHandler virtual method is called.

Finally, the message reaches the appropriate processing procedure, where the processing intended for it is performed. By using keyword Inherited it is further sent for processing in ancestors. After that, the message also gets into the DefaultHandler method, which performs the final actions on message processing and passes it to the DefWindowProc (DefMDIProc) procedure for standard Windows processing.

Handling Messages with Delphi Components
Thus, short description message processing sequence is as follows. All messages initially pass through the method whose address is specified in the WindowProc property. By default, this is the WndProc method. After that, they are separated and sent according to their message methods. At the end, they converge again in the DefaultHandler method if they were not processed earlier or the inherited handler (Inherited) is called in the handlers. Therefore, Delphi components have the following capabilities for handling messages:
a) Before any message handler sees the message. In this case, either the replacement of the method address in the WindowProc property or the replacement of the TControl.WndProc method is required.
The WindowProc property is declared as follows:

Toure TWndMethod= procedure(Var Message: TMessage) of object;
property WindowProc: TWndMethod;

In fact, using the WindowProc property, you can create a method of type TWndMethod and temporarily replace the original method with the created one, however, since the method address is not stored in the WindowProc property, you must first store the address of the original WndProc method so that it can be restored later.

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;

b) Inside the corresponding message method.
Let's take another similar example.
Use the message sent to components to redraw WMPAINT.

In the TForml class, we will declare this method in order to override it and present the implementation of the overridden message method:

Type TForml=Class(TForm)
… // All other necessary declarations
protected
Procedure WMPaint(Var Msg: TWMPaint); Message WM_PAINT; end;
Procedure TForml.WMPaint(Var Msg: TWMPaint); Begin
If CheckBox1.Checked Then ShowMessage('O6pa6o message checker!');
Inherited;
end;

When overriding specific message handlers, it's always a good idea to call Inherited to perform the basic message processing that Windows needs.

c) After each of the methods corresponding to the message sees it.

In this case, you must override the DefaultHandler.

procedure DefaultHandler(varMessage); 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;

Communication between messages and events
Many Delphi VCL events are directly related to Windows messages. IN help system Delphi lists these matches. They are presented in table.1.

Table 1

VCL eventWindows messageVCL eventWindows message
OnActivateWM_ACTIVATEOnKeyPressWM_CHAR
onclickWM_LBUTTONDOWNOnKeyUpWM_KEYUP
OnCreateWM_CREATEOnPaintWM_PAINT
OnDblClickWM_LBUTTONDBLCLKOnResizeWM_SIZE
OnKeyDownWM_KEYDOWNOnTimerWM_TIMER

You should not create message handlers if there is a predefined event for it. In these cases, it makes sense to use event handling because it is less restrictive.

When developing applications, there may be a situation in which an application needs to send a message either to itself or to another user application. Some may be puzzled by the previous statement: why should an application send a message to itself when you can simply call the appropriate procedure? This good question, and there are several answers to it. First, the use of messages is a mechanism to support real polymorphism because it does not require any knowledge of the type of the object receiving the message. Thus, the messaging technology has the same power as the virtual method mechanism, but has much more flexibility. Secondly, messages allow optional processing - if the recipient object does not process the incoming message, then nothing terrible will happen. And, third, messages allow you to broadcast to multiple recipients and organize parallel listening, which is quite difficult to implement using the procedure call mechanism.

Using Messages Within an Application

Making an application send a message to itself is very simple - just use the functions API interface Win32 SendMessage() or PostMessage(), or the Perform() method. The message must have an ID in the range WM_USER+100 to $7FFFF (which Windows reserves for user messages). For example: 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);

Then, to intercept this message, create a normal handler procedure that performs the necessary actions in the form:

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);

As you can see from the example, there is little difference in how a native message is processed from a standard Windows message. They consist of using identifiers ranging from WM_USER+100 and above, as well as giving each message a name that will somehow reflect its meaning.

Never send messages with a WM_USER value greater than $7FFF unless you are absolutely certain that the recipient is capable of processing the message correctly. Since each window can independently choose the values ​​it uses, subtle bugs are very likely to occur unless you create tables of message identifiers in advance that all senders and receivers of messages will work with.

Messaging between applications

If you need to exchange messages between two or more applications, you should use the RegisterWindowMessage() API function in them. This method ensures that for a given message type, each application will use the same message number(message number).

The RegisterWindowMessage() function takes as a parameter a string with

terminated by a null character and returns an ID in the range $C000 - $FFFF for the new message. This means that calling this function with the same string as a parameter in any application will be enough to guarantee the same message numbers in all applications participating in the exchange. Another advantage of such a function is that the system guarantees that the identifier assigned to any given row is unique. This allows broadcast messages to be sent to all existing windows in the system without fear of unwanted side effects. disadvantage this method is some complication of processing such messages. The bottom line is that the message ID is only known when the application is running, so using the standard message handling routines is not possible. To work with such messages, you need to redefine standard methods WndProc() or DefaultHandler() controls, or the corresponding window class procedures.

ON A NOTE

The number returned by the RegisterWindowMessage() function is dynamically generated and can take various meanings in different Windows sessions, which means that it cannot be determined until the program is executed.

Broadcast messages

Any class derived from the TWinControl class allows using the Broadcast() method to send broadcast message(broadcast message) to any control of which it is the owner. This technique is used when it is required to send the same message to a group of components. For example, to send a custom message named um_Foo to all controls that belong to the Panel1 object, you can use the following code:

Message:= UM_FOO;

Develop a program that will provide an interface for using the Win2000/XP standard net send messaging command. Allow the user to specify the recipient's address, message text, and the number of messages to be sent. Also provide for the possibility of setting a block to receive messages from other computers.

Form development

Create a new Delphi project. Change the form title (Caption property) to Net Sender. Place three Label components of the category one above the other along the left edge of the form standard and set their Caption property to IP Address:, Message:, and Quantity:.

Next to each of the labels, place an Edit component of the category standard. Name the top one ip (Name property), and assign the value 192.168.0.1 to the Text property; name the middle field txt, and assign some default message text to the Text property; Name the bottom field how and set the Text property to 1.

Under the listed components, place the category Checkbox component standard. Name it secure, set the Caption property to Disable receiving messages, and set the Checked property to True.

Place a button at the very bottom of the form (the Button component of the standard) by setting its Caption property to Send. We also need a timer (the Timer component of the System), for which the Interval property should be set to 10.

The resulting form should correspond to Fig. 15.1.

Rice. 15.1. Form for the program to send messages to local network

Program code development

First of all, let's write our own bomb procedure that will read all the settings and send a message. Declare this procedure as a private member of the form class:

We also need a global variable i of type integer:

Now let's create an implementation of the bomb procedure in the implementation section:

procedure TForm1.bomb();
if how.Text= "" then how.Text:= "1";
if ip.Text = "" then ip.Text:= "127.0.0.1";(if the ip-address is not specified, then we send it to the local computer)
WinExec(PChar("net send " + ip.Text + """ + txt.Text + """), 0);//send message

This procedure checks whether all required fields are filled in. If there is no message text, then set the sign "!"; if the IP address is not specified, then we send a message to the local computer with the address 127.0.0.1; if the number of messages is not specified, then we send one message. Messages are sent using the standard net send command, which has the following syntax:

net send ip address message.

Now let's handle the timer's OnTimer event:

h: HWND;//stores window ID
if not secure.Checked then//if the checkbox is not checked
Timer1.Enabled:= False;//disable monitoring
if secure.Checked then//if the checkbox is checked
//look for windows with messages
h:= FindWindow(nil, "Messaging Service ");// close all found windows
if h<>

If the Disable receiving messages checkbox is checked, then we start monitoring windows whose title says that this is a message, and close all found windows. If the checkbox is not checked, monitoring is disabled.

In order to be able to switch between these two modes, you need to create a secure.OnClick event handler:

if secure.Checked then//if the checkbox is checked...
Timer1.Enabled:= True;//... enable monitoring

When you press a button send we'll just call the bomb procedure:

In order to make life easier for the user, we will make sure that the message is also sent by pressing the key in any text input field. To do this, you need to create an OnKeyPress event handler for each of the fields. The code for this handler for the ip field, which can then be assigned to the txt and how fields:

if key= #13 then//if a key is pressed
bomb;//send message

Full module source code

The complete code for the LAN messaging program module is shown in Listing 15.1.

Listing 15.1. LAN messaging program module

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);


// check if the text message is not empty
if txt.Text = "" then txt.Text:= "!";
//if the quantity is not specified, then send one message
if how.Text= "" then how.Text:= "1";
if ip.Text = "" then ip.Text:= "127.0.0.1"; (if the ip-address is not specified, then we send it to the local computer)
//send the specified number of messages
for i:=1 to StrToInt(how.Text) do
WinExec(PChar("net send " + ip.Text + """ + txt.Text + """), 0); //send message

procedure TForm1.Timer1Timer(Sender: TObject);
h: HWND; //stores window ID
if not secure.Checked then //if the checkbox is not checked
Timer1.Enabled:= False; //disable monitoring
if secure.Checked then //if the checkbox is checked
//look for windows with messages
h:= FindWindow(nil, "Messaging Service "); // close all found windows
if h<>0 then PostMessage(h, WM_QUIT, 0, 0);

procedure TForm1.secureClick(Sender: TObject);
if secure.Checked then //if the checkbox is checked...
Timer1.Enabled:= True; //... enable monitoring

procedure TForm1.ipKeyPress(Sender: TObject; var Key: Char);
if key = #13 then //if a key is pressed
bomb; //send message

procedure TForm1.Button1Click(Sender: TObject);

⊚ All project files and the executable file of the considered program are located on the CD attached to the book in the Chapter 15 folder.

Often, delphi programs use email. This article will fully explain how your email is sent to another user. using delphi. In this case, we will use only standard Delphi components.

To begin with, let's create a new project and name it \"Sending emails using delphi\". Then, several components 1x Memo, 3x Edit, 2x Botton must be transferred to the form, and IdSMTP, IdAntiFreeze, IdMessage must also be transferred. Next, on the onclick event of any button, we write:

//choose SMTP server. IN this moment costs yandex. IdSMTP1.Host:= "smtp.yandex.ru"; //your login (for some it is necessary to write with a domain). IdSMTP1.Username:=" [email protected]"; //mail password. IdSMTP1.Password:= "qwerty123"; //port, we recommend using 587. IdSMTP1.Port:=587; //the subject of the message will fit into Edit2. IdMessage1.Subject:= Edit2.Text; // Edit1 will contain the recipient's address. IdMessage1.Recipients.EMailAddresses:= Edit1.Text; //your email from which the sender is going. IdMessage1.From.Address:= " [email protected]"; // memo1 will contain the text you want to send. IdMessage1.Body.Text:= memo1.Text ; // Edit3 will contain your electronic signature(Name). IdMessage1.From.Name:= Edit3.Text; //connect IdSMTP1.connect; //send IdSMTP1.Send(IdMessage1); //disconnect IdSMTP1.Disconnect;

If IdMessage displays question marks

This bug is related to the fact that you enter Russian letters, and the memo component cannot read them correctly, for this you need to specify the Utf8 encoding.

// set encoding IdMessage1.Charset:="UTF-8"; // translate the text into the required encoding IdMessage1.Body.Text:=UTF8Encode(memo1.text);

somewhere like that

IdTCPClient1.Host:= "127.0.0.1"; IdTCPClient1.Connect;// connected IdTCPClient1.Socket.WriteLn("command"); // sent command command and line feed //Wait for response and close connection txtResults.Lines.Append(IdTCPClient1.Socket.ReadLn); IdTCPClient1.Disconnect;

in this case, the command is just text with a newline. This makes it much easier to receive a command from the other side (just ReadLn). In the general case, you need to invent (or use a ready-made) protocol.

above it was a client. And now the server. With the server, things are a little more complicated. It is clear that it is normal for a server to serve more than one client, many. And there are several "schemes" for this.

    Classic - one client - one thread. The circuit is easy to code, intuitive. It is well parallelized across the cores. The disadvantage is that it is usually very difficult to create many threads, and this limits the number of clients. For 32-bit programs, the upper limit is somewhere around 1500 (one and a half thousand) threads per process. But in this case, the overhead for switching them can "eat" the entire percentage. This is the scheme used in indy.

    The second classical - all clients on one flow. This scheme is often more complex in coding, but with the right approach, it allows you to keep 20-30k "slow users" with practically no load on the core. A strong plus of this scheme is that you can do without mutexes and other synchronization primitives. This scheme is used by NodeJS and standard classes for networking in Qt.

    Mixed. In this case, several threads are created, each of which serves a certain number of clients. The most difficult in coding, but allows you to use iron resources to the maximum.

How it's done in Indy. Indy tcp server for each connection creates a separate thread (TThread) and further work with the client goes in it. Indy hides this nicely, leaving only the need for the user to implement the IdTCPServer.onExecute method. But, as I said above, this method is launched in a separate thread, and each client has his own personal one. This means the following:

  • sleep can be called in this method and only one client will wait. All the rest will work (but if you call sleep in the button click handler, then the result is known)
    • it is better to access global variables only through synchronization primitives.
    • gui elements must be handled carefully and correctly. It is better not to do it directly (some components allow you to access them from other threads, but you need to carefully read the docks).
    • you need to access other clients through a lock (because if two threads want to write to the same user, nothing good will come of it).

Let's look at a very simple example. We answer any client request in the same way and close the connection (such a echo server).

Procedure TForm1.IdTCPServer1Execute(AContext: TIdContext); var strText: String; begin //Receive a string from the client strText:= AContext.Connection.Socket.ReadLn; //Answer AContext.Connection.Socket.WriteLn(strText); //Close the connection with the user AContext.Connection.Disconnect; end;

AContext is a special object that contains all the necessary information about the client. The idTcpServer itself contains a list of these contexts and can be accessed. Let's consider a more complex broadcast. That is, send one message to everyone

VarClients:TList; i: integer; begin // foolproof :) if not Assigned(IdTCPServer1.Contexts) then exit; // get a list of clients and lock it Clients:=IdTCPServer1.Contexts.LockList; try for i:= 0 to Clients.Count-1 do try //LBuffer is of type TBytes and contains prepared data to send // but WriteLn can also be used. TIdContext(Clients[i]).Connection.IOHandler.Write(LBuffer); except // logic needs to be added here. The client may disconnect during end; finally // important! the list must be unlocked, otherwise other methods will not be able to go beyond Contexts.LockList IdTCPServer1.Contexts.UnlockList; end; end;

indie contains BytesToString() and ToBytes() to convert String and TIdBytes into each other.

The list is locked so that others cannot modify it. Otherwise, the cycle itself becomes much more complicated. And most importantly, do not forget to unlock!

The last question remains. How to send a message to a specific client. To do this, you need to learn how to identify the connection. This can be done in several ways - look at the ip / port. But there is better. IdContext (more precisely, its ancestor idTask) has a Data property of type TObject. You can write your object to it and store all the necessary data there. Typical example usage will be next. When the client has just connected, this field is empty. When it passed the name-password check, we create an object (our own), save the name there and write it to the Data property. And then, when you need to loop through the connected clients, we simply subtract it. Of course, if there are thousands of users, it will be expensive to view all users every time. But how to do it more optimally is the topic of another large article.