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 new project Delphi. 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 LAN messaging program
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
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 moduleWindows, 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.
In this article, we will consider methods for sending and receiving mail using Delphi. Receiving and sending mail is quite simple to implement, but there are still many pitfalls, so it's best to read this article carefully. To send mail, we need a component idSMTP from the page IndyClients component palettes Delphi. This component implements everything needed to send Email protocol SMTP (Simple Mail Transfer Protocol), it usually uses port 25, but it can be changed to another one (Port property). It is also necessary to make settings for the form (size, color, etc.)
Let's start implementing our client, for this we will place on the form idSMTP .Also customize the appearance of the form (name, size, etc.). If necessary, you can change the port, but the main thing is not to forget that this port must be unblocked in the firewall. To connect withSMTPserver, you must specify its host (property host ). For example: IdSMTP1.Host:="smtp .mail.ru"; Or (as we will do) place on the form Label andEdit. Looking ahead a bit, I’ll say that the connection to the server is carried out by the method Connect.
Example:
procedureconnect(constATimeout :integer);override;
Where ATimeout- optional parameter, sets the maximum time in milliseconds to wait for a response from SMTP server, after which the attempt to establish a connection is terminated. For example: IdSMTP1.Connect(5000);
If authorization is required when connecting to the server, then the value of the property AuthenticationType needs to be installed in atLogin, while in the object inspector you also need to define properties username( Username). For example, Username mailbox [email protected] then the username will be in this case Delphi and Password (the password for the box), or this action can be done programmatically.
IdSMTP1.AuthenticationType:=atLogin;
IdSMTP1.Username:="delphi";
IdSMTP1.Password:="password";
IdSMTP1.AuthenticationType:=atNone;
In our example, we will assume that we will need a login and a password for authorization.
After applying the method Connect you need to check the result of its execution. This is done using the property connected, if it is true , then the connection went well.
The function is used to send messages. send.
Example:
IdSMTP1.Send(Msg);
Now let's take a closer look at the structure of the letter. As mentioned earlier, the send method sends a message body, which is a structure of type TIdMessage. The very structure of the letter in Delphi is implemented by separate components TIdMessage. It's on the Component Palette. Indy Misc.
Definition example TIdMessage structures:
var
Msg: TIdMessage;
begin
Msg.Subject:="message subject"; //message subject text
Msg.Recipients.EMailAddresses:=" [email protected]";
//specify the recipient's address
Msg.From.Address:=" [email protected]"; //specify the author of the letter
Msg.Body.Text:="message text"; //post the text of the message
Msg.Date:=StrToDate("12/01/2004");//the date the letter was sent can be any
end;
Property subject defines the subject of the message. Property Recipients includes property EMailAddresses it defines the addressees. That is, simply to whom the letter is intended, a comma serves as a separator between two or more mailing addresses.
Example:
Msg.Recipients.EMailAddresses:="[email protected], [email protected] ";
Property From is an object of type TIdEmailAddressItem it contains information about the sender of the letter. This property includes three properties: Name, Address, Text. Property address contains information about the sender's email address, it has the type String.
Example:
Msg.From.Address:=" [email protected]";
Property name is the name of the sender, is of type String.
Example:
Msg.From.Name:="Ivan Ivanovich";
Property Text contains the combined information of these two properties. Now let's move on to the body of the letter, it has the type TStrings. Now let's talk about attaching files to a letter, that is, about attachment. If you need to attach a file to a letter, you will need to create an object of the class TidAttachment. To do this, you will need to use the view constructor:
constructor Create(Collection: TIdMessageParts; const AFileName: TFileName = ""); reintroduce;
Where Collection is a collection of attachments to the letter, its type TIdMessageParts. The AFileName constant of type TFileName is a regular text string. It must specify the correct path to the file.
Example:
TIdAttachment.Create(Msg.MessageParts,"c:file.zip");
After sending the message, it is desirable to break the connection with the server so as not to load the communication channel. Breaking the connection is done using the Disconnect method.
Example:
IdSMTP1.Disconnect;
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 stream. 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 creates a separate thread (TThread) for each connection 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. A typical use case would be the following. 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.
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 point, you can provide special handling of the message, 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. With the help of the Inherited keyword, it is further sent for processing in the 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, a brief description of the 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. The Delphi help system lists these mappings. They are presented in table.1.
Table 1
VCL event | Windows message | VCL event | Windows message |
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 |
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 is a 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 as easy as using either the SendMessage() or PostMessage() Win32 API functions, 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. Because 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, they should use the RegisterWindowMessage() API function. 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 processing routines is not possible. To work with such messages, you will need to override the standard WndProc() or DefaultHandler() methods of controls, or the corresponding procedures of the window class.
ON A NOTE
The number returned by the RegisterWindowMessage() function is created dynamically and can take on different values in different Windows sessions, which means 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;