Talking about the application of "flow" in Delphi programming
Chen Jingtao
What is a stream? Stream, simply put, is an abstract data processing tool based on object-oriented. In the stream, some basic operations for processing data are defined, such as reading data, writing data, etc. The programmer performs all operations on the stream without caring about the actual flow direction of the data at the other end of the stream. Streams can not only process files, but also dynamic memory, network data and other data forms. If you are very proficient in stream operations and use the convenience of streams in your program, the efficiency of writing programs will be greatly improved.
Below, the author uses four examples: EXE file encryptor, electronic greeting card, self-made OICQ and network screen transmission to illustrate the use of "stream" in Delphi programming. Some of the techniques in these examples were once secrets of many software and not open to the public. Now everyone can directly quote the code for free.
"Tall buildings rise from the ground." Before analyzing the examples, let's first understand the basic concepts and functions of flow. Only after understanding these basic things can we proceed to the next step. Please be sure to understand these basic methods carefully. Of course, if you are already familiar with them, you can skip this step.
1. Basic concepts and function declarations of streams in Delphi
In Delphi, the base class of all stream objects is the TStream class, which defines the common properties and methods of all streams.
The properties defined in the TStream class are introduced as follows:
1. Size: This property returns the size of the data in the stream in bytes.
2. Position: This attribute controls the position of the access pointer in the flow.
There are four virtual methods defined in Tstream:
1. Read: This method reads data from the stream. The function prototype is:
Function Read(var Buffer;Count:Longint):Longint;virtual;abstract;
The parameter Buffer is the buffer placed when reading data. Count is the number of bytes of data to be read. The return value of this method is the actual number of bytes read, which can be less than or equal to the value specified in Count.
2. Write: This method writes data into the stream. The function prototype is:
Function Write(var Buffer;Count:Longint):Longint;virtual;abstract;
The parameter Buffer is the buffer of the data to be written into the stream, Count is the length of the data in bytes, and the return value of this method is the number of bytes actually written into the stream.
3. Seek: This method implements the movement of the read pointer in the stream. The function prototype is:
Function Seek(Offset:Longint;Origint:Word):Longint;virtual;abstract;
The parameter Offset is the number of offset bytes, and the parameter Origin points out the actual meaning of Offset. Its possible values are as follows:
soFromBeginning:Offset is the position of the pointer from the beginning of the data after movement. At this time Offset must be greater than or equal to zero.
soFromCurrent:Offset is the relative position of the pointer after movement and the current pointer.
soFromEnd:Offset is the position of the pointer from the end of the data after movement. At this time Offset must be less than or equal to zero. The return value of this method is the position of the pointer after movement.
4. Setsize: This method changes the size of the data. The function prototype is:
Function Setsize(NewSize:Longint);virtual;
In addition, several static methods are also defined in the TStream class:
1. ReadBuffer: The function of this method is to read data from the current position in the stream. The function prototype is:
PRocedure ReadBuffer(var Buffer;Count:Longint);
The definition of parameters is the same as Read above. Note: When the number of data bytes read is not the same as the number of bytes that need to be read, an EReadError exception will be generated.
2. WriteBuffer: The function of this method is to write data to the stream at the current position. The function prototype is:
Procedure WriteBuffer(var Buffer;Count:Longint);
The definition of parameters is the same as Write above. Note: When the number of data bytes written is not the same as the number of bytes that need to be written, an EWriteError exception will be generated.
3. CopyFrom: This method is used to copy data streams from other streams. The function prototype is:
Function CopyFrom(Source:TStream;Count:Longint):Longint;
The parameter Source is the stream that provides data, and Count is the number of data bytes copied. When Count is greater than 0, CopyFrom copies Count bytes of data from the current position of the Source parameter; when Count is equal to 0, CopyFrom sets the Position property of the Source parameter to 0, and then copies all the data of the Source;
TStream has other derived classes, the most commonly used of which is the TFileStream class. To use the TFileStream class to access files, you must first create an instance. The statement is as follows:
constructor Create(const Filename:string;Mode:Word);
Filename is the file name (including path), and the parameter Mode is the way to open the file, which includes the file opening mode and sharing mode. Its possible values and meanings are as follows:
Open mode:
fmCreate: Create a file with the specified file name, or open the file if it already exists.
fmOpenRead: Open the specified file in read-only mode
fmOpenWrite: Open the specified file in write-only mode
fmOpenReadWrite: Open the specified file for writing
Sharing mode:
fmShareCompat: Share mode is compatible with FCBs
fmShareExclusive: Do not allow other programs to open the file in any way
fmShareDenyWrite: Do not allow other programs to open the file for writing
fmShareDenyRead: Do not allow other programs to open the file in read mode
fmShareDenyNone: Other programs can open the file in any way
TStream also has a derived class, TMemoryStream, which is used very frequently in actual applications. It's called a memory stream, which means creating a stream object in memory. Its basic methods and functions are the same as above.
Well, with the above foundation in place, we can start our programming journey.
-------------------------------------------------- --------------------------
2. Practical application one: using streams to create EXE file encryptors, bundles, self-extracting files and installation programs
Let’s first talk about how to make an EXE file encryptor.
The principle of the EXE file encryptor: Create two files, one is used to add resources to the other EXE file, which is called an add-in program. Another EXE file that is added is called a header file. The function of this program is to read the files added to itself. The EXE file structure under Windows is relatively complex, and some programs also have checksums. When they find that they have been changed, they will think that they are infected by a virus and refuse to execute. So we add the file to our own program so that the original file structure will not be changed. Let's first write an add function. The function of this function is to add a file as a stream to the end of another file. The function is as follows:
Function Cjt_AddtoFile(SourceFile,TargetFile:string):Boolean;
var
Target,Source:TFileStream;
MyFileSize:integer;
begin
try
Source:=TFileStream.Create(SourceFile,fmOpenRead or fmShareExclusive);
Target:=TFileStream.Create(TargetFile,fmOpenWrite or fmShareExclusive);
try
Target.Seek(0,soFromEnd);//Add resources to the end
Target.CopyFrom(Source,0);
MyFileSize:=Source.Size+Sizeof(MyFileSize);//Calculate the resource size and write it to the end of the auxiliary process
Target.WriteBuffer(MyFileSize,sizeof(MyFileSize));
finally
Target.Free;
Source.Free;
end;
except
Result:=False;
Exit;
end;
Result:=True;
end;
With the above foundation, we should easily understand this function. The parameter SourceFile is the file to be added, and the parameter TargetFile is the target file to be added. For example, to add a.exe to b.exe: Cjt_AddtoFile('a.exe',b.exe'); If the addition is successful, it will return True, otherwise it will return False.
Based on the above function we can write the opposite read function:
Function Cjt_LoadFromFile(SourceFile,TargetFile:string):Boolean;
var
Source:TFileStream;
Target:TMemoryStream;
MyFileSize:integer;
begin
try
Target:=TMemoryStream.Create;
Source:=TFileStream.Create(SourceFile,fmOpenRead or fmShareDenyNone);
try
Source.Seek(-sizeof(MyFileSize),soFromEnd);
Source.ReadBuffer(MyFileSize,sizeof(MyFileSize));//Read resource size
Source.Seek(-MyFileSize,soFromEnd);//Locate the resource location
Target.CopyFrom(Source,MyFileSize-sizeof(MyFileSize));//Remove resources
Target.SaveToFile(TargetFile);//Save to file
finally
Target.Free;
Source.Free;
end;
except
Result:=false;
Exit;
end;
Result:=true;
end;
The parameter SourceFile is the name of the file to which the file has been added, and the parameter TargetFile is the name of the target file saved after the file is taken out. For example, Cjt_LoadFromFile('b.exe','a.txt'); takes out the file in b.exe and saves it as a.txt. If the extraction is successful, it returns True otherwise it returns False.
Open Delphi, create a new project, and put an Edit control Edit1 and two Buttons on the window: Button1 and Button2. Button's Caption property is set to "OK" and "Cancel" respectively. Write code in the Click event of Button1:
var S: string;
begin
S:=ChangeFileExt(application.ExeName,'.Cjt');
if Edit1.Text='790617' then
begin
Cjt_LoadFromFile(Application.ExeName,S);
{Take out the file and save it in the current path and name it "original file.Cjt"}
Winexec(pchar(S),SW_Show);{Run "original file.Cjt"}
Application.Terminate;{Exit program}
end
else
Application.MessageBox('Password is incorrect, please re-enter!', 'Password is incorrect', MB_ICONERROR+MB_OK);
Compile this program and rename the EXE file to head.exe. Create a new text file head.rc with the content: head exefile head.exe, then copy them to the BIN directory of Delphi, execute the Dos command Brcc32.exe head.rc, and a head.res file will be generated. This file is Keep the resource files we want first.
Our header file has been created, let's create the add-in program.
Create a new project and place the following controls: an Edit, an Opendialog, and the Caption properties of the two Button1s are set to "Select File" and "Encryption" respectively. Add a sentence in the source program: {$R head.res} and copy the head.res file to the current directory of the program. In this way, the head.exe just now is compiled together with the program.
Write the code in the Cilck event of Button1:
if OpenDialog1.Execute then Edit1.Text:=OpenDialog1.FileName;
Write the code in the Cilck event of Button2:
var S:String;
begin
S:=ExtractFilePath(Edit1.Text);
if ExtractRes('exefile','head',S+'head.exe') then
if Cjt_AddtoFile(Edit1.Text,S+'head.exe') then
if DeleteFile(Edit1.Text) then
if RenameFile(S+'head.exe',Edit1.Text) then
Application.MessageBox('File encryption successful!','Message',MB_ICONINFORMATION+MB_OK)
else
begin
if FileExists(S+'head.exe') then DeleteFile(S+'head.exe');
Application.MessageBox('File encryption failed!','Message',MB_ICONINFORMATION+MB_OK)
end;
end;
Among them, ExtractRes is a custom function, which is used to extract head.exe from the resource file.
Function ExtractRes(ResType, ResName, ResNewName : String):boolean;
var
Res : TResourceStream;
begin
try
Res := TResourceStream.Create(Hinstance, Resname, Pchar(ResType));
try
Res.SavetoFile(ResNewName);
Result:=true;
finally
Res.Free;
end;
except
Result:=false;
end;
end;
Note: Our function above simply appends one file to the end of another file. In actual applications, it can be changed to add multiple files, as long as the offset address is defined according to the actual size and number. For example, a file bundler adds two or more programs to a header file. The principles of self-extracting programs and installers are the same, but with more compression. For example, we can reference a LAH unit, compress the stream and then add it, so that the file will become smaller. Just decompress it before reading it out. In addition, the example of the EXE encryptor in the article still has many imperfections. For example, the password is fixed to "790617", and after taking out the EXE and running it, it should be deleted after it has finished running, etc. Readers can modify it by themselves.
-------------------------------------------------- -------------------
3. Practical Application 2: Using streams to create executable e-greeting cards
We often see some e-greeting card production software that allows you to select pictures yourself, and then it will generate an EXE executable file for you. When you open the greeting card, the picture will be displayed while playing music. Now that we have learned stream operations, we can also make one.
In the process of adding images, we can directly use the previous Cjt_AddtoFile, and what we need to do now is how to read out and display the images. We can use the previous Cjt_LoadFromFile to read out the image first, save it as a file and then load it in. However, there is a simpler way, which is to directly read the file stream and display it. With the powerful tool of stream, everything becomes simple.
The most popular pictures nowadays are BMP format and JPG format. We will now write reading and display functions for these two kinds of pictures.
Function Cjt_BmpLoad(ImgBmp:TImage;SourceFile:String):Boolean;
var
Source:TFileStream;
MyFileSize:integer;
begin
Source:=TFileStream.Create(SourceFile,fmOpenRead or fmShareDenyNone);
try
try
Source.Seek(-sizeof(MyFileSize),soFromEnd);
Source.ReadBuffer(MyFileSize,sizeof(MyFileSize));//Read resources
Source.Seek(-MyFileSize,soFromEnd);//Locate the starting position of the resource
ImgBmp.Picture.Bitmap.LoadFromStream(Source);
finally
Source.Free;
end;
except
Result:=False;
Exit;
end;
Result:=True;
end;
The above is a function for reading BMP images, and the following is a function for reading JPG images. Because the JPG unit is used, a sentence: uses jpeg must be added to the program.
Function Cjt_JpgLoad(JpgImg:Timage;SourceFile:String):Boolean;
var
Source:TFileStream;
MyFileSize:integer;
Myjpg: TJpegImage;
begin
try
Myjpg:= TJpegImage.Create;
Source:=TFileStream.Create(SourceFile,fmOpenRead or fmShareDenyNone);
try
Source.Seek(-sizeof(MyFileSize),soFromEnd);
Source.ReadBuffer(MyFileSize,sizeof(MyFileSize));
Source.Seek(-MyFileSize,soFromEnd);
Myjpg.LoadFromStream(Source);
JpgImg.Picture.Bitmap.Assign(Myjpg);
finally
Source.Free;
Myjpg.free;
end;
except
Result:=false;
Exit;
end;
Result:=true;
end;
With these two functions, we can make a readout program. Let’s take BMP pictures as an example:
Run Delphi, create a new project, and place an image display control Image1. Just write the following sentence in the window's Create event:
Cjt_BmpLoad(Image1,Application.ExeName);
This is the header file, and then we use the previous method to generate a head.res resource file.
Now we can start making our add-on program. The entire code is as follows:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ExtCtrls, StdCtrls, ExtDlgs;
type
TForm1 = class(TForm)
Edit1: TEdit;
Button1: TButton;
Button2: TButton;
OpenPictureDialog1: TOpenPictureDialog;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
Function ExtractRes(ResType, ResName, ResNewName : String):boolean;
Function Cjt_AddtoFile(SourceFile,TargetFile:string):Boolean;
{Private declarations}
public
{Public declarations}
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
Function TForm1.ExtractRes(ResType, ResName, ResNewName : String):boolean;
var
Res : TResourceStream;
begin
try
Res := TResourceStream.Create(Hinstance, Resname, Pchar(ResType));
try
Res.SavetoFile(ResNewName);
Result:=true;
finally
Res.Free;
end;
except
Result:=false;
end;
end;
Function TForm1.Cjt_AddtoFile(SourceFile,TargetFile:string):Boolean;
var
Target,Source:TFileStream;
MyFileSize:integer;
begin
try
Source:=TFileStream.Create(SourceFile,fmOpenRead or fmShareExclusive);
Target:=TFileStream.Create(TargetFile,fmOpenWrite or fmShareExclusive);
try
Target.Seek(0,soFromEnd);//Add resources to the end
Target.CopyFrom(Source,0);
MyFileSize:=Source.Size+Sizeof(MyFileSize);//Calculate the resource size and write it to the end of the auxiliary process
Target.WriteBuffer(MyFileSize,sizeof(MyFileSize));
finally
Target.Free;
Source.Free;
end;
except
Result:=False;
Exit;
end;
Result:=True;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Caption:='Bmp2Exe demonstration program. Author: Chen Jingtao';
Edit1.Text:='';
OpenPictureDialog1.DefaultExt := GraphicExtension(TBitmap);
OpenPictureDialog1.Filter := GraphicFilter(TBitmap);
Button1.Caption:='Select BMP picture';
Button2.Caption:='Generate EXE';
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
if OpenPictureDialog1.Execute then
Edit1.Text:=OpenPictureDialog1.FileName;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
HeadTemp:String;
begin
if Not FileExists(Edit1.Text) then
begin
Application.MessageBox('BMP image file does not exist, please select again!','Message',MB_ICONINFORMATION+MB_OK)
Exit;
end;
HeadTemp:=ChangeFileExt(Edit1.Text,'.exe');
if ExtractRes('exefile','head',HeadTemp) then
if Cjt_AddtoFile(Edit1.Text,HeadTemp) then
Application.MessageBox('EXE file generated successfully!','Message',MB_ICONINFORMATION+MB_OK)
else
begin
if FileExists(HeadTemp) then DeleteFile(HeadTemp);
Application.MessageBox('EXE file generation failed!','Message',MB_ICONINFORMATION+MB_OK)
end;
end;
end.
How about it? It's amazing :) Make the program interface more beautiful and add some functions, and you will find that it is not much inferior to those software that requires registration.
-------------------------------------------------- --------------------------
Practical application three: Use streams to make your own OICQ
OICQ is an online real-time communication software developed by Shenzhen Tencent Company and has a large user base in China. However, OICQ must be connected to the Internet and logged into Tencent's server before it can be used. So we can write one ourselves and use it in the local network.
OICQ uses the UDP protocol, which is a connectionless protocol, that is, the communicating parties can send information without establishing a connection, so the efficiency is relatively high. The NMUDP control of FastNEt that comes with Delphi itself is a user datagram control of the UDP protocol. However, it should be noted that if you use this control, you must exit the program before shutting down the computer, because the TNMXXX control has a BUG. The ThreadTimer used by PowerSocket, the basis of all nm controls, uses a hidden window (class TmrWindowClass) to deal with flaws.
What went wrong:
Psock::TThreadTimer::WndProc(var msg:TMessage)
if msg.message=WM_TIMER then
He handles it himself
msg.result:=0
else
msg.result:=DefWindowProc(0,....)
end
The problem is that when calling DefWindowProc, the transmitted HWND parameter is actually a constant 0, so DefWindowProc actually cannot work. The call to any input message returns 0, including WM_QUERYENDsession, so windows cannot exit. Due to the abnormal call of DefWindowProc, in fact, except for WM_TIMER, other messages processed by DefWindowProc are invalid.
The solution is in PSock.pas
Within TThreadTimer.Wndproc
Result := DefWindowProc( 0, Msg, WPARAM, LPARAM );
Change to:
Result := DefWindowProc( FWindowHandle, Msg, WPARAM, LPARAM );
Early low-level versions of OICQ also had this problem. If OICQ was not turned off, the screen would flash for a moment and then return when the computer was turned off.
Okay, without further ado, let’s write our OICQ. This is actually an example that comes with Delphi:)
Create a new project, drag an NMUDP control from the FASTNET panel to the window, and then place three EDITs named Editip, EditPort, EditMyTxt, three buttons BtSend, BtClear, BtSave, a MEMOMemoReceive, a SaveDialog and a status bar. StatusBar1. When the user clicks BtSend, a memory stream object is created, the text information to be sent is written into the memory stream, and then NMUDP sends the stream out. When NMUDP receives data, its DataReceived event is triggered. Here we convert the received stream into character information and then display it.
Note: Remember to release (Free) all stream objects after they are created and used. In fact, its destructor should be Destroy. However, if the creation of the stream fails, using Destroy will generate an exception. If using Free, the program will first check whether If the stream is not successfully established, it will be released only if it is established, so it is safer to use Free.
In this program we use the NMUDP control, which has several important properties. RemoteHost represents the IP or computer name of the remote computer, and LocalPort is the local port, which mainly monitors whether there is incoming data. The RemotePort is a remote port, and data is sent through this port when sending data. Understanding these can already understand our program.
The entire code is as follows:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,StdCtrls, ComCtrls,NMUDP;
type
TForm1 = class(TForm)
NMUDP1: TNMUDP;
EditIP: TEdit;
EditPort: TEdit;
EditMyTxt: TEdit;
MemoReceive: TMemo;
BtSend: TButton;
BtClear: TButton;
BtSave: TButton;
StatusBar1: TStatusBar;
SaveDialog1: TSaveDialog;
procedure BtSendClick(Sender: TObject);
procedure NMUDP1DataReceived(Sender: TComponent; NumberBytes: Integer;
FromIP: String; Port: Integer);
procedure NMUDP1InvalidHost(var handled: Boolean);
procedure NMUDP1DataSend(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure BtClearClick(Sender: TObject);
procedure BtSaveClick(Sender: TObject);
procedure EditMyTxtKeyPress(Sender: TObject; var Key: Char);
private
{Private declarations}
public
{Public declarations}
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.BtSendClick(Sender: TObject);
var
MyStream: TMemoryStream;
MySendTxt: String;
Iport,icode:integer;
Begin
Val(EditPort.Text,Iport,icode);
if icode<>0 then
begin
Application.MessageBox('The port must be a number, please re-enter!','Message',MB_ICONINFORMATION+MB_OK);
Exit;
end;
NMUDP1.RemoteHost := EditIP.Text; {remote host}
NMUDP1.LocalPort:=Iport; {local port}
NMUDP1.RemotePort := Iport; {remote port}
MySendTxt := EditMyTxt.Text;
MyStream := TMemoryStream.Create; {Create stream}
try
MyStream.Write(MySendTxt[1], Length(EditMyTxt.Text));{Write data}
NMUDP1.SendStream(MyStream); {Send stream}
finally
MyStream.Free; {release stream}
end;
end;
procedure TForm1.NMUDP1DataReceived(Sender: TComponent;
NumberBytes: Integer; FromIP: String; Port: Integer);
var
MyStream: TMemoryStream;
MyReciveTxt: String;
begin
MyStream := TMemoryStream.Create; {Create stream}
try
NMUDP1.ReadStream(MyStream);{Receive stream}
SetLength(MyReciveTxt,NumberBytes);{NumberBytes is the number of bytes received}
MyStream.Read(MyReciveTxt[1],NumberBytes);{read data}
MemoReceive.Lines.Add('Received information from host '+FromIP+':'+MyReciveTxt);
finally
MyStream.Free; {release stream}
end;
end;
procedure TForm1.NMUDP1InvalidHost(var handled: Boolean);
begin
Application.MessageBox('The other party's IP address is incorrect, please re-enter!','Message',MB_ICONINFORMATION+MB_OK);
end;
procedure TForm1.NMUDP1DataSend(Sender: TObject);
begin
StatusBar1.SimpleText:='Message sent successfully!';
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
EditIP.Text:='127.0.0.1';
EditPort.Text:='8868';
BtSend.Caption:='Send';
BtClear.Caption:='Clear chat history';
BtSave.Caption:='Save chat history';
MemoReceive.ScrollBars:=ssBoth;
MemoReceive.Clear;
EditMyTxt.Text:='Enter information here and click Send.';
StatusBar1.SimplePanel:=true;
end;
procedure TForm1.BtClearClick(Sender: TObject);
begin
MemoReceive.Clear;
end;
procedure TForm1.BtSaveClick(Sender: TObject);
begin
if SaveDialog1.Execute then MemoReceive.Lines.SaveToFile(SaveDialog1.FileName);
end;
procedure TForm1.EditMyTxtKeyPress(Sender: TObject; var Key: Char);
begin
if Key=#13 then BtSend.Click;
end;
end.
The above program is certainly far from OICQ, because OICQ uses Socket5 communication method. When it goes online, it first retrieves friend information and online status from the server. When sending times out, it will save the information on the server first, wait for the other party to go online next time, then send it and then delete the server's backup. You can improve this program based on the concepts you learned earlier. For example, add an NMUDP control to manage the online status. The sent information is first converted into ASCII code for AND or operation and adds a header information. The receiver will judge after receiving the information. Whether the information header is correct or not, the information will be decrypted and displayed only if it is correct, thus improving security and confidentiality.
In addition, another great advantage of the UDP protocol is that it can broadcast, which means that anyone in the same network segment can receive information without specifying a specific IP address. Network segments are generally divided into three categories: A, B, and C.
1~126.XXX.XXX.XXX (Class A network): The broadcast address is XXX.255.255.255
128~191.XXX.XXX.XXX (Class B network): The broadcast address is XXX.XXX.255.255
192~254.XXX.XXX.XXX (Category C network): The broadcast address is XXX.XXX.XXX.255
For example, if three computers are 192.168.0.1, 192.168.0.10, and 192.168.0.18, when sending information, you only need to specify the IP address 192.168.0.255 to achieve broadcasting. Below is a function that converts IP to broadcast IP. Use it to improve your own OICQ^-^.
Function Trun_ip(S:string):string;
var s1,s2,s3,ss,sss,Head: string;
n,m:integer;
begin
sss:=S;
n:=pos('.',s);
s1:=copy(s,1,n);
m:=length(s1);
delete(s,1,m);
Head:=copy(s1,1,(length(s1)-1));
n:=pos('.',s);
s2:=copy(s,1,n);
m:=length(s2);
delete(s,1,m);
n:=pos('.',s);
s3:=copy(s,1,n);
m:=length(s3);
delete(s,1,m);
ss:=sss;
if strtoint(Head) in [1..126] then ss:=s1+'255.255.255'; //1~126.255.255.255 (Class A network)
if strtoint(Head) in [128..191] then ss:=s1+s2+'255.255';//128~191.XXX.255.255 (Class B network)
if strtoint(Head) in [192..254] then ss:=s1+s2+s3+'255'; //192~254.XXX.XXX.255 (Category network)
Result:=ss;
end;
-------------------------------------------------- --------------------------
5. Practical Application 4: Using streams to transmit screen images over the network
You should have seen many network management programs. One of the functions of such programs is to monitor the screen of a remote computer. In fact, this is also achieved using stream operations. Below we give an example. This example is divided into two programs, one is the server and the other is the client. After the program is compiled, it can be used directly on a single machine, a local network or the Internet. Corresponding comments have been given in the program. We will make a detailed analysis later.
Create a new project and drag a ServerSocket control to the window on the Internet panel. This control is mainly used to monitor the client and establish connections and communications with the client. After setting the listening port, call the method Open or Active:=True to start working. Note: Unlike the previous NMUDP, once the Socket starts listening, its port cannot be changed. If you want to change it, you must first call Close or set Active to False, otherwise an exception will occur. In addition, if the port is already open, this port cannot be used anymore. Therefore, you cannot run the program again before it exits, otherwise an exception will occur, that is, an error window will pop up. In practical applications, errors can be avoided by determining whether the program has been run and exiting if it is already running.
When data is passed in from the client, the ServerSocket1ClientRead event will be triggered, and we can process the received data here. In this program, it mainly receives the character information sent by the client and performs corresponding operations according to the prior agreement.
The entire program code is as follows:
unit Unit1;{server program}
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, JPEG, ExtCtrls, ScktComp;
type
TForm1 = class(TForm)
ServerSocket1: TServerSocket;
procedure ServerSocket1ClientRead(Sender: TObject;Socket: TCustomWinSocket);
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
procedure Cjt_GetScreen(var Mybmp: TBitmap; DrawCur: Boolean);
{Customized screen capture function, DrawCur indicates whether to capture the mouse image}
{Private declarations}
public
{Public declarations}
end;
var
Form1: TForm1;
MyStream: TMemorystream;{memory stream object}
implementation
{$R *.DFM}
procedure TForm1.Cjt_GetScreen(var Mybmp: TBitmap; DrawCur: Boolean);
var
Cursorx, Cursory: integer;
dc:hdc;
Mycan: Tcanvas;
R: TRect;
DrawPos: TPoint;
MyCursor: TIcon;
hld: hwnd;
Threadld: dword;
mp: tpoint;
pIconInfo: TIconInfo;
begin
Mybmp := Tbitmap.Create; {Create BMPMAP}
Mycan := TCanvas.Create; {screen capture}
dc := GetWindowDC(0);
try
Mycan.Handle := dc;
R := Rect(0, 0, screen.Width, screen.Height);
Mybmp.Width := R.Right;
Mybmp.Height := R.Bottom;
Mybmp.Canvas.CopyRect(R, Mycan, R);
finally
releaseDC(0, DC);
end;
Mycan.Handle := 0;
Mycan.Free;
if DrawCur then {Draw the mouse image}
begin
GetCursorPos(DrawPos);
MyCursor := TIcon.Create;
getcursorpos(mp);
hld := WindowFromPoint(mp);
Threadld := GetWindowThreadProcessId(hld, nil);
AttachThreadInput(GetCurrentThreadId, Threadld, True);
MyCursor.Handle := Getcursor();
AttachThreadInput(GetCurrentThreadId, threadld, False);
GetIconInfo(Mycursor.Handle, pIconInfo);
cursorx := DrawPos.x - round(pIconInfo.xHotspot);
cursory := DrawPos.y - round(pIconInfo.yHotspot);
Mybmp.Canvas.Draw(cursorx, cursory, MyCursor); {Draw on the mouse}
DeleteObject(pIconInfo.hbmColor);{GetIconInfo creates two bitmap objects when used. These two objects need to be released manually}
DeleteObject(pIconInfo.hbmMask); {Otherwise, after calling it, it will create a bitmap, and multiple calls will generate multiple ones until the resources are exhausted}
Mycursor.ReleaseHandle; {Release array memory}
MyCursor.Free; {release mouse pointer}
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
ServerSocket1.Port := 3000; {port}
ServerSocket1.Open; {Socket starts listening}
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if ServerSocket1.Active then ServerSocket1.Close; {Close Socket}
end;
procedure TForm1.ServerSocket1ClientRead(Sender: TObject;
Socket: TCustomWinSocket);
var
S, S1: string;
MyBmp: TBitmap;
Myjpg: TJpegimage;
begin
S := Socket.ReceiveText;
if S = 'cap' then {The client issues a screen capture command}
begin
try
MyStream := TMemorystream.Create;{Create memory stream}
MyBmp := TBitmap.Create;
Myjpg := TJpegimage.Create;
Cjt_GetScreen(MyBmp, True); {True means grabbing the mouse image}
Myjpg.Assign(MyBmp); {Convert BMP images to JPG format for easy transmission on the Internet}
Myjpg.CompressionQuality := 10; {JPG file compression percentage setting, the larger the number, the clearer the image, but the larger the data}
Myjpg.SaveToStream(MyStream); {Write JPG image to stream}
Myjpg.free;
MyStream.Position := 0;{Note: This sentence must be added}
s1 := inttostr(MyStream.size);{size of stream}
Socket.sendtext(s1); {send stream size}
finally
MyBmp.free;
end;
end;
if s = 'ready' then {The client is ready to receive images}
begin
MyStream.Position := 0;
Socket.SendStream(MyStream); {Send the stream}
end;
end;
end.
The above is the server, let's write the client program below. Create a new project and add the Socket control ClientSocket, the image display control Image, a Panel, an Edit, two Buttons and a status bar control StatusBar1. Note: Place Edit1 and two Buttons above Panel1. The properties of ClientSocket are similar to ServerSocket, but there is an additional Address property, indicating the IP address of the server to be connected. After filling in the IP address, click "Connect" to establish a connection with the server program. If successful, communication can begin. Clicking "Screen Capture" will send characters to the server. Because the program uses JPEG image units, Jpeg must be added to Uses.
The entire code is as follows:
unit Unit2{client};
interface
uses
Windows,Messages,SysUtils,Classes,Graphics,Controls,Forms,Dialogs,StdCtrls,ScktComp,ExtCtrls,Jpeg, ComCtrls;
type
TForm1 = class(TForm)
ClientSocket1: TClientSocket;
Image1: TImage;
StatusBar1: TStatusBar;
Panel1: TPanel;
Edit1: TEdit;
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure ClientSocket1Connect(Sender: TObject;
Socket: TCustomWinSocket);
procedure Button2Click(Sender: TObject);
procedure ClientSocket1Error(Sender: TObject; Socket: TCustomWinSocket;
ErrorEvent: TErrorEvent; var ErrorCode: Integer);
procedure ClientSocket1Read(Sender: TObject; Socket: TCustomWinSocket);
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure ClientSocket1Disconnect(Sender: TObject;
Socket: TCustomWinSocket);
private
{Private declarations}
public
{Public declarations}
end;
var
Form1: TForm1;
MySize: Longint;
MyStream: TMemorystream;{memory stream object}
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
{-------- The following is to set the appearance properties of the window control------------- }
{Note: Place Button1, Button2 and Edit1 above Panel1}
Edit1.Text := '127.0.0.1';
Button1.Caption := 'Connect to host';
Button2.Caption := 'Screen capture';
Button2.Enabled := false;
Panel1.Align := alTop;
Image1.Align := alClient;
Image1.Stretch := True;
StatusBar1.Align:=alBottom;
StatusBar1.SimplePanel := True;
{----------------------------------------------------------}
MyStream := TMemorystream.Create; {Create a memory stream object}
MySize := 0; {initialization}
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
if not ClientSocket1.Active then
begin
ClientSocket1.Address := Edit1.Text; {remote IP address}
ClientSocket1.Port := 3000; {Socket port}
ClientSocket1.Open; {Establish connection}
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
Clientsocket1.Socket.SendText('cap'); {Send a command to notify the server to capture the screen image}
Button2.Enabled := False;
end;
procedure TForm1.ClientSocket1Connect(Sender: TObject;
Socket: TCustomWinSocket);
begin
StatusBar1.SimpleText := 'With host' + ClientSocket1.Address + 'Connection successfully established!';
Button2.Enabled := True;
end;
procedure TForm1.ClientSocket1Error(Sender: TObject;
Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
varErrorCode: Integer);
begin
Errorcode := 0; {Do not pop up error window}
StatusBar1.SimpleText := 'Unable to connect with host' + ClientSocket1.Address + 'Establish connection!';
end;
procedure TForm1.ClientSocket1Disconnect(Sender: TObject;
Socket: TCustomWinSocket);
begin
StatusBar1.SimpleText := 'With host' + ClientSocket1.Address + 'Disconnect!';
Button2.Enabled := False;
end;
procedure TForm1.ClientSocket1Read(Sender: TObject;
Socket: TCustomWinSocket);
var
MyBuffer: array[0..10000] of byte; {Set receive buffer}
MyReceviceLength: integer;
S: string;
MyBmp: TBitmap;
MyJpg: TJpegimage;
begin
StatusBar1.SimpleText := 'Receiving data...';
if MySize = 0 then {MySize is the number of bytes sent by the server. If it is 0, it means that image reception has not yet started}
begin
S := Socket.ReceiveText;
MySize := Strtoint(S); {Set the number of bytes to be received}
Clientsocket1.Socket.SendText('ready'); {Send a command to notify the server to start sending images}
end
else
begin {The following is the image data receiving part}
MyReceviceLength := socket.ReceiveLength; {read packet length}
StatusBar1.SimpleText := 'Receiving data, data size is:' + inttostr(MySize);
Socket.ReceiveBuf(MyBuffer, MyReceviceLength); {Receive the data packet and read it into the buffer}
MyStream.Write(MyBuffer, MyReceviceLength); {Write data into the stream}
if MyStream.Size >= MySize then {If the stream length is greater than the number of bytes to be received, the reception is completed}
begin
MyStream.Position := 0;
MyBmp := tbitmap.Create;
MyJpg := tjpegimage.Create;
try
MyJpg.LoadFromStream(MyStream); {Read the data in the stream into the JPG image object}
MyBmp.Assign(MyJpg); {Convert JPG to BMP}
StatusBar1.SimpleText := 'Displaying image';
Image1.Picture.Bitmap.Assign(MyBmp); {Assigned to image1 element}
finally {The following is the cleanup work}
MyBmp.free;
MyJpg.free;
Button2.Enabled := true;
{Socket.SendText('cap');Add this sentence to capture the screen continuously}
MyStream.Clear;
MySize := 0;
end;
end;
end;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
MyStream.Free; {Release the memory stream object}
if ClientSocket1.Active then ClientSocket1.Close; {Close Socket connection}
end;
end.
Program principle: Run the server to start listening, then run the client, enter the server IP address to establish a connection, and then send a character to notify the server to capture the screen. The server calls the custom function Cjt_GetScreen to capture the screen and save it as BMP, convert the BMP to JPG, write the JPG into the memory stream, and then send the stream to the client. After receiving the stream, the client performs the opposite operation, converting the stream to JPG and then to BMP and then displaying it.
Note: Due to the limitations of Socket, too large data cannot be sent at one time, but can only be sent several times. Therefore, in the program, after converting the screen capture into a stream, the server first sends the size of the stream to notify the client of how big the stream is. The client uses this number to determine whether the stream has been received. If it is, it will be converted and displayed.
This program and the previous self-made OICQ both use the memory stream object TMemoryStream. In fact, this stream object is the most commonly used in programming. It can improve I/O reading and writing capabilities, and if you want to operate several different types of streams at the same time and exchange data with each other, use it as a "middleman" It's the best. For example, if you compress or decompress a stream, you first create a TMemoryStream object, then copy other data into it, and then perform the corresponding operations. Because it works directly in memory, the efficiency is very high. Sometimes you won't even notice any delay.
Areas for improvement in the program: Of course, you can add a compression unit to compress before sending. Note: There is also a trick here, which is to compress BMP directly instead of converting it to JPG and then compress it. Experiments have shown that the size of an image in the above program is about 40-50KB. If it is processed using the LAH compression algorithm, it will only be 8-12KB, which makes the transmission faster. If you want to go faster, you can use this method: first capture the first image and send it, and then start from the second image and only send images in different areas from the previous one. There is a foreign program called Remote Administrator that uses this method. The data they tested is as follows: 100-500 frames per second on the local network, and 5-10 frames per second on the Internet when the network speed is extremely low. These digressions just want to illustrate one truth: when thinking about a problem, especially when writing a program, especially a program that looks very complicated, don't get too caught up in it. Sometimes you might as well think about it from another angle. Program is dead, talent is alive. Of course, these can only rely on the accumulation of experience. But forming good habits from the beginning will pay dividends throughout your life!