Delphi. Sending mail using Delphi

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

Form development

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

Place an Edit category component next to each label Standard. Name the top one ip (Name property), and assign the Text property the value 192.168.0.1.; name the middle field txt, and assign the Text property to some default message text; Name the bottommost field how, and set the Text property to 1.

Under the listed components, place the Category Checkbox component Standard. Name it secure, set its Caption property to Disable Message Reception, and its Checked property to True.

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

The resulting shape 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, which 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 to 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 an 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 OnTimer event:

h: HWND;//stores the 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 message boxes
h:= FindWindow(nil, "Message Service");//close all found windows
if h<>

If the Disable receiving messages checkbox is selected, then we begin monitoring windows whose title indicates 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 the button is pressed Send we will simply 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 a 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 is 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 of the program module for sending messages over a local network is presented in Listing 15.1.

Listing 15.1. Program module for sending messages over a local network

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 it's empty text message
if txt.Text = "" then txt.Text:= "!";
//if the quantity is not specified, then we 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 the 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 message boxes
h:= FindWindow(nil, "Message 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 the key is pressed
bomb; //send message

procedure TForm1.Button1Click(Sender: TObject);

⊚ All project files and the executable file of the program discussed are located on the CD included with the book in the Chapter 15 folder.

Sending messages

As well as Windows system sends its messages to various windows, the application itself may also need to exchange messages between its own windows and controls. There are several ways to send messages: the PerForm() method (which works independently of the Windows API), as well as the Win32 API functions SendMessage() and PostMessage().

The PerForm() method, which all descendants of the TControl class have:

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

To send a message to a form or control, use the following syntax:

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

When PerForm() is called, control will not return to the calling program until the message has been processed. This method passes the message without going through the Windows API messaging system.

API functions SendMessage() and PostMessage() declared in the module Windows next way:

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 – message recipient window handle; Msg – message identifier; wParam and lParam – additional data.

The SendMessage() function, like the PerForm() method, sends a message directly to the window procedure and waits for it to be processed, while the PostMessage() function places the message in a message queue and returns control to the program that called it without waiting for processing results.

The SendMessage() function returns the value obtained as a result of processing the message. PostMessage() function - returns a value indicating whether the message was successfully posted to the message queue.

User messages

When developing applications, there may be a situation in which an application needs to send a special message either to itself or to another application in order to perform some action. For user-created messages, Windows reserves values ​​from WM_USER to $7FFF.

Example of a custom message:

TestMsg = WM_USER + 100; // message ID

TForm1 = class(TForm)

// message processing method:

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

procedure TForm1.MyMessage(var Msg: TMessage);

ShowMessage("TestMsg message running");

Msg.Result:= 1; // returned result

Examples of sending a message to the form:

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

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

ShowMessage("Message processed successfully");

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

ShowMessage("Message has been placed in the message queue");

Delphi Events

An event is something that happens while a program is running. From the point of view of the Delphi language, an event is a property of a procedural type, and its value is a pointer to some method. Assigning a value to such a property means specifying the address of the method that will be executed when the event occurs. Such methods are called event handlers.

Using events allows you to add new functionality to an existing class without creating a descendant class.

Event properties try to start with the word "On" followed by the event name.

Relationship between messages and events

Delphi provides an interface for interacting with Windows messages, at least some of them. Many VCL component events are directly related to messages Windows type WM_XXX.

Sequence of message processing in Delphi
All Delphi classes have a built-in message handling mechanism called message handlers. A 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.

Once a message is received, the VCL message 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 (in fact, it 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 API function DispatchMessage(Msg). This does not happen when sending a message.

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

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

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

Delphi provides a main non-virtual method MainWndProc(Var Message: TMessage) for each component window. It contains an exception handling block, passing the message structure from Windows to a 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. From this point on, you can provide special processing of the message if required by the logic of your program. Typically at this point the processing is modified to prevent standard VCL processing from taking place.

By default, the value of the WindowProc property of an object is initialized to the address of the WndProc virtual method. Next, if there are no registered message interceptors of type TWindowHook, WndProc calls the virtual method TObject.Dispatch, which, using the Msg field of the incoming message structure, determines whether the message is in the list of message handlers for this object. If the object is not processing the message, the list of ancestor message handlers is examined. If such a method is eventually found, 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 carried out. By using keyword Inherited it is further sent for processing in ancestors. The message then also goes to the DefaultHandler method, which does the final message processing and passes it to the DefWindowProc (DefMDIProc) procedure for standard Windows processing.

Processing messages with Delphi components
Thus, brief description 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 which they are separated and sent according to their own 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 There are the following options for processing messages:
a) Before any message handler sees the message. In this case, you either need to replace the method address in the WindowProc property or replace the TControl.WndProc method.
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 in the WindowProc property is not stored, you must first save 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 give another similar example.
Uses the message sent to components to redraw WMPAINT.

In the TForml class, we will declare this method for the purpose of overriding it and present the implementation of the overridden message method:

Ture 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(‘O6pa6ot message!’);
Inherited;
End;

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

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

In this case, it is necessary to override the 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;

Relationship between messages and events
Many VCL Delphi events are directly related to Windows messages. The Delphi help system 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 shouldn't create message handlers if there is a predefined event for it. In such cases, it makes sense to use event handling because it has fewer restrictions.

somewhere like this

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

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

above it was a client. And now the server. With the server, everything is a little more complicated. It is clear that it is normal for a server to serve not just one client, but many. And there are several “schemes” for this.

    Classic - one client - one thread. The scheme is easy to code and intuitive. It is well parallelized across 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 costs of switching them can “eat up” the entire percentage. This is the scheme used in indy.

    The second classic one is all clients on one thread. This scheme is often more complex to code, but with the right approach it allows you to keep 20-30k “slow users” with virtually no load on the kernel. A strong advantage of this scheme is that you can do without mutexes and other synchronization primitives. This scheme is used by NodeJS and standard classes for working with the network in Qt.

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

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

  • in this method you can call sleep and only one client will wait. All others 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.
    • You need to access gui elements carefully and correctly. It’s better not to do it directly (some components allow access to them from other threads, but you need to read the docs carefully).
    • You need to access other clients through blocking (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 respond to any client request in kind and close the connection (sort of an echo server).

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

AContext is a special object that contains all 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

Var Clients: TList; i:integer; begin // fool proof :) 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 for sending //but WriteLn can also be used. TIdContext(Clients[i]).Connection.IOHandler.Write(LBuffer); except // you need to add logic here. The client may disconnect during the process end; finally // important! the list must be unlocked, otherwise other methods will not be able to go further Contexts.LockList IdTCPServer1.Contexts.UnlockList; end; end;

indie contains BytesToString() and ToBytes() for converting 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, don’t forget to unlock!

There is one last question left. How to send a message to a specific client. To do this, you need to learn 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 into it and store all the necessary data there. A typical use case would be as follows. When the client has just connected, this field is empty. When it passes the name-password check, we create an object (our own), save the name there and write it in the Data property. And then, when you need to loop through connected clients, we simply read it. Of course, if there are thousands of users, it will be expensive to view all users every time. But how to do this more optimally is the topic of another large article.

Sending and receiving mail is quite simple using Delphi. To send mail, we need the idSMTP component from the Indy Clients page of the Delphi component palette.

This component implements everything needed to send email via the SMTP protocol (Simple Mail Transfer Protocol), using port 25, through which commands and letter text are sent. The steps for sending email are as follows:

1) connection to the SMTP server on port 25;
2) preparing the body of the letter, identifying the sender and recipient of the letter;
3) sending a letter to the SMTP server;

Having placed the idSMTP component on the form, let’s configure it. The port can be configured in the object inspector by setting the Port property to 25, or you can do the same in the program code:

IdSMTP1.Port:=25;

Connection to the server

To connect to the SMTP server that will send our mail, you need to specify its URL; for the mail.ru server this is done as follows:

IdSMTP1.Host:= ′smtp.mail.ru′;

The connection to the server is made using the Connect method:


procedure Connect(const ATimeout: Integer); override;

where ATimeout is an optional parameter that specifies the maximum time in milliseconds to wait for a response from the 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 AuthenticationType property must be set to atLogin, while in the object inspector you also need to define the Username properties (user name. For example, Username mailbox [email protected]- delphi) and Password (mailbox password), or do the same programmatically:

IdSMTP1.AuthenticationType:=atLogin;
IdSMTP1.Username:=′delphi′;
IdSMTP1.Password:='something';

IdSMTP1.AuthenticationType:=atNone;

After using the Connect method, you need to analyze the logical property Connected, which is set to True if the connection is successful. After this, you can send a message using the Send method:

if Connected=True then IdSMTP1.Send(Msg);

Letter structure

The Send method sends the message body, which is a structure of type TIdMessage;

The message structure is implemented in Delphi by a separate TIdMessage component located on the Indy Misc component palette and looks like this

TidMessage The TIdMessage structure is defined as follows:

I think everything is clear with the subject of the message. Property

The names of the electronic accounts to which the letter is addressed are specifically determined. Names must be specified using a separator of the form "," that is, separated by a comma. For example:

For example,

For example,

The Text property contains information from both properties. The body of the message is an object of type TStrings:

where Collection is an object of the TIdMessageParts class, which is a collection of applications to email.
constant AFileName of type TFileName - is a regular text string indicating the correct path to the file, for example "C:file.zip", default value is ′′.

Thus, continuing our example with a line like

Share