Delphi プログラミングにおける「フロー」の応用について語る
チェン・ジンタオ
ストリームとは何ですか?ストリームとは、簡単に言えば、オブジェクト指向に基づいた抽象的なデータ処理ツールです。ストリームでは、データの読み取り、データの書き込みなど、データを処理するためのいくつかの基本操作が定義されます。プログラマは、ストリームのもう一方の端でのデータの実際のフロー方向を気にせずに、ストリーム上ですべての操作を実行します。ストリームはファイルを処理できるだけでなく、動的メモリ、ネットワーク データ、その他のデータ形式も処理できます。ストリーム操作に熟達し、プログラム内でストリームの利便性を利用すると、プログラム作成の効率が大幅に向上します。
以下に著者は、EXE ファイル暗号化、電子グリーティング カード、自作 OICQ、ネットワーク画面送信の 4 つの例を使用して、Delphi プログラミングにおける「ストリーム」の使用法を説明します。これらの例のテクニックの一部は、かつては多くのソフトウェアの秘密であり、公開されていませんでしたが、現在は誰でも無料でコードを直接引用できます。
「高い建物が地面からそびえ立っています。」 例を分析する前に、まずフローの基本的な概念と機能を理解してから、次のステップに進むことができます。基本的な方法をしっかりと理解してください。もちろん、すでに慣れている場合は、このステップをスキップできます。
1. Delphi のストリームの基本概念と関数宣言
Delphi では、すべてのストリーム オブジェクトの基本クラスは TStream クラスであり、すべてのストリームに共通のプロパティとメソッドを定義します。
TStream クラスで定義されるプロパティは次のように導入されます。
1. サイズ: このプロパティは、ストリーム内のデータのサイズをバイト単位で返します。
2. 位置: この属性は、フロー内のアクセス ポインターの位置を制御します。
Tstream には 4 つの仮想メソッドが定義されています。
1. 読み取り: このメソッドはストリームからデータを読み取ります。関数のプロトタイプは次のとおりです。
関数 Read(var Buffer;Count:Longint):Longint;virtual;abstract;
パラメータ Buffer は、データが読み取られるときに配置されるバッファです。 Count は、読み取られるデータのバイト数です。このメソッドの戻り値は、で指定した値以下になる可能性があります。カウント。
2. Write: このメソッドはデータをストリームに書き込みます。関数のプロトタイプは次のとおりです。
関数 Write(var Buffer;Count:Longint):Longint;virtual;abstract;
パラメーター Buffer はストリームに書き込まれるデータのバッファー、Count はデータの長さ (バイト単位)、このメソッドの戻り値はストリームに実際に書き込まれるバイト数です。
3. Seek: このメソッドは、ストリーム内の読み取りポインタの移動を実装します。関数のプロトタイプは次のとおりです。
Function Seek(オフセット:倍長整数;原点:単語):倍長整数;仮想;抽象;
Offset パラメータはオフセットのバイト数で、Origin パラメータは Offset の実際の意味を示します。
soFromBeginning:Offset は、移動後のデータの先頭からのポインタの位置です。このとき、Offset は 0 以上である必要があります。
soFromCurrent:Offset は、移動後のポインタと現在のポインタの相対位置です。
soFromEnd:Offset は移動後のデータの終端からのポインタの位置です。このとき、Offset はゼロ以下でなければなりません。このメソッドの戻り値は、移動後のポインタの位置です。
4. Setsize: このメソッドはデータのサイズを変更します。関数のプロトタイプは次のとおりです。
関数 Setsize(NewSize:Longint);virtual;
さらに、いくつかの静的メソッドも TStream クラスで定義されています。
1. ReadBuffer: このメソッドの機能は、ストリーム内の現在位置からデータを読み取ることです。関数のプロトタイプは次のとおりです。
PROcedure ReadBuffer(var Buffer;Count:Longint);
パラメータの定義は上記のReadと同じです。注: 読み取られたデータのバイト数が、読み取る必要があるバイト数と同じでない場合、EReadError 例外が生成されます。
2. WriteBuffer: このメソッドの機能は、ストリームの現在位置にデータを書き込むことです。関数のプロトタイプは次のとおりです。
プロシージャ WriteBuffer(var Buffer;Count:Longint);
パラメータの定義は上記の書き込みと同じです。注: 書き込まれたデータのバイト数が、書き込む必要があるバイト数と同じでない場合、EWriteError 例外が生成されます。
3. CopyFrom: このメソッドは、他のストリームからデータ ストリームをコピーするために使用されます。関数のプロトタイプは次のとおりです。
関数 CopyFrom(ソース:TStream;カウント:倍長整数):倍長整数;
パラメータ Source はデータを提供するストリームで、Count はコピーされたデータ バイト数です。 Count が 0 より大きい場合、CopyFrom は Source パラメーターの現在位置から Count バイトのデータをコピーします。Count が 0 に等しい場合、CopyFrom は Source パラメーターの Position プロパティを 0 に設定し、Source のすべてのデータをコピーします。 ;
TStream には他にも派生クラスがあり、最もよく使用されるのは TFileStream クラスです。 TFileStream クラスを使用してファイルにアクセスするには、まずインスタンスを作成する必要があります。声明は次のとおりです。
コンストラクター Create(const ファイル名:文字列;モード:Word);
Filename はファイル名 (パスを含む)、パラメータ Mode はファイルを開く方法であり、ファイルを開くモードと共有モードが含まれます。その可能な値と意味は次のとおりです。
オープンモード:
fmCreate: 指定されたファイル名でファイルを作成するか、ファイルが既に存在する場合はそのファイルを開きます。
fmOpenRead: 指定されたファイルを読み取り専用モードで開きます
fmOpenWrite: 指定されたファイルを書き込み専用モードで開きます
fmOpenReadWrite: 指定されたファイルを書き込み用に開きます
共有モード:
fmShareCompat: 共有モードは FCB と互換性があります
fmShareExclusive: 他のプログラムがファイルを開くことをいかなる方法でも許可しません
fmShareDenyWrite: 他のプログラムが書き込みのためにファイルを開くことを許可しません
fmShareDenyRead: 他のプログラムが読み取りモードでファイルを開くことを許可しません
fmShareDenyNone: 他のプログラムがどのような方法でもファイルを開くことができます
TStream には、TMemoryStream という派生クラスもあり、実際のアプリケーションで頻繁に使用されます。これはメモリ ストリームと呼ばれ、メモリ内にストリーム オブジェクトを作成することを意味します。基本的なメソッドと機能は上記と同じです。
さて、上記の基礎が整ったら、プログラミングの旅を始めることができます。
-------------------------------------------------- ------------------------
2. 実用的なアプリケーション 1: ストリームを使用して EXE ファイル暗号化プログラム、バンドル、自己解凍ファイル、インストール プログラムを作成する
まず、EXE ファイル暗号化プログラムの作成方法について説明します。
EXE ファイル暗号化機能の原理: 2 つのファイルを作成します。1 つはアドイン プログラムと呼ばれる、もう 1 つの EXE ファイルにリソースを追加するために使用されます。追加される別の EXE ファイルはヘッダー ファイルと呼ばれます。このプログラムの機能は、それ自体に追加されたファイルを読み取ることです。 Windows の EXE ファイル構造は比較的複雑で、一部のプログラムにはチェックサムが変更されていることが分かると、ウイルスに感染していると考えられ、実行が拒否されます。そこで、元のファイル構造が変更されないように、ファイルを独自のプログラムに追加します。まず、追加関数を作成しましょう。この関数の機能は、ファイルをストリームとして別のファイルの末尾に追加することです。機能は次のとおりです。
関数 Cjt_AddtoFile(SourceFile,TargetFile:string):Boolean;
変数
ターゲット、ソース:TFileStream;
MyFileSize:整数;
始める
試す
Source:=TFileStream.Create(SourceFile,fmOpenRead または fmShareExclusive);
Target:=TFileStream.Create(TargetFile、fmOpenWrite または fmShareExclusive);
試す
Target.Seek(0,soFromEnd);//リソースを最後に追加します
Target.CopyFrom(ソース,0);
MyFileSize:=Source.Size+Sizeof(MyFileSize);//リソース サイズを計算し、補助プロセスの最後に書き込みます。
Target.WriteBuffer(MyFileSize,sizeof(MyFileSize));
ついに
ターゲット。無料。
出典.無料;
終わり;
を除外する
結果:=偽;
出口;
終わり;
結果:=真;
終わり;
上記の基礎があれば、この機能は簡単に理解できるはずです。パラメータ SourceFile は追加されるファイルであり、パラメータ TargetFile は追加されるターゲット ファイルです。たとえば、a.exe を b.exe に追加するには: Cjt_AddtoFile('a.exe',b.exe'); 追加が成功した場合は True を返し、それ以外の場合は False を返します。
上記の関数に基づいて、逆の読み取り関数を作成できます。
関数 Cjt_LoadFromFile(SourceFile,TargetFile:string):Boolean;
変数
ソース:TFileStream;
ターゲット:TMemoryStream;
MyFileSize:整数;
始める
試す
ターゲット:=TMemoryStream.Create;
Source:=TFileStream.Create(SourceFile,fmOpenRead または fmShareDenyNone);
試す
Source.Seek(-sizeof(MyFileSize),soFromEnd);
Source.ReadBuffer(MyFileSize,sizeof(MyFileSize));// リソース サイズの読み取り
Source.Seek(-MyFileSize,soFromEnd);//リソースの場所を特定します
Target.CopyFrom(Source,MyFileSize-sizeof(MyFileSize));// リソースを削除します
Target.SaveToFile(TargetFile);// ファイルに保存
ついに
ターゲット。無料。
出典.無料;
終わり;
を除外する
結果:=false;
出口;
終わり;
結果:= true;
終わり;
パラメータSourceFileはファイルが追加されたファイルの名前であり、パラメータTargetFileはファイルが取り出された後に保存されたターゲットファイルの名前です。たとえば、 Cjt_LoadFromFile('b.exe','a.txt'); は、b.exe 内のファイルを取り出し、a.txt として保存します。抽出が成功した場合は True を返し、そうでない場合は False を返します。
Delphi を開き、新しいプロジェクトを作成し、編集コントロール Edit1 と 2 つのボタン (Button1 と Button2) をウィンドウに配置します。ボタンの Caption プロパティはそれぞれ「OK」と「Cancel」に設定されます。 Button1 の Click イベントにコードを記述します。
var S: 文字列;
始める
S:=ChangeFileExt(application.ExeName,'.Cjt');
Edit1.Text='790617' の場合、
始める
Cjt_LoadFromFile(アプリケーション.Exe名,S);
{ファイルを取り出して現在のパスに保存し、「元のファイル.Cjt」という名前を付けます}
Winexec(pchar(S),SW_Show);{「元のファイル.Cjt」を実行}
Application.Terminate;{プログラムの終了}
終わり
それ以外
Application.MessageBox('パスワードが間違っています。再入力してください!', 'パスワードが間違っています', MB_ICONERROR+MB_OK);
このプログラムをコンパイルし、EXE ファイルの名前を head.exe に変更します。 head exefile head.exe という内容の新しいテキスト ファイル head.rc を作成し、それらを Delphi の BIN ディレクトリにコピーし、Dos コマンド Brcc32.exe head.rc を実行すると、head.res ファイルが生成されます。ファイルは最初に必要なリソース ファイルを保持します。
ヘッダー ファイルが作成されました。アドイン プログラムを作成しましょう。
新しいプロジェクトを作成し、次のコントロールを配置します。編集、開くダイアログ、および 2 つの Button1 のキャプション プロパティがそれぞれ「ファイルの選択」と「暗号化」に設定されます。ソース プログラムに {$R head.res} という文を追加し、head.res ファイルをプログラムの現在のディレクトリにコピーします。このようにして、先ほどのhead.exeがプログラムと一緒にコンパイルされます。
Button1 の Cilck イベントにコードを記述します。
OpenDialog1.Execute の場合、Edit1.Text:=OpenDialog1.FileName;
Button2 の Cilck イベントにコードを記述します。
var S:文字列;
始める
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('ファイルの暗号化が成功しました!','メッセージ',MB_ICONINFORMATION+MB_OK)
それ以外
始める
if FileExists(S+'head.exe') then DeleteFile(S+'head.exe');
Application.MessageBox('ファイルの暗号化に失敗しました!','メッセージ',MB_ICONINFORMATION+MB_OK)
終わり;
終わり;
このうち、ExtractRes はカスタム関数で、リソース ファイルから head.exe を抽出するために使用されます。
関数 ExtractRes(ResType, ResName, ResNewName : String):boolean;
変数
解像度 : TResourceStream;
始める
試す
Res := TResourceStream.Create(Hinstance, Resname, Pchar(ResType));
試す
Res.SavetoFile(ResNewName);
結果:= true;
ついに
解像度無料。
終わり;
を除外する
結果:=false;
終わり;
終わり;
注: 上記の関数は、あるファイルを別のファイルの末尾に追加するだけです。実際のアプリケーションでは、実際のサイズと数に従ってオフセット アドレスが定義されている限り、複数のファイルを追加するように変更できます。たとえば、ファイル バンドラーは 2 つ以上のプログラムをヘッダー ファイルに追加します。自己解凍プログラムとインストーラーの原理は同じですが、圧縮率が高くなります。たとえば、LAH ユニットを参照し、ストリームを圧縮してから追加すると、ファイルが小さくなります。読み出す前に解凍してください。さらに、記事内の EXE 暗号化プログラムの例には、パスワードが「790617」に固定されている、EXE を取り出して実行した後、実行が終了したら削除する必要があるなど、まだ多くの不完全な点があります。読者は自分で変更することができます。
-------------------------------------------------- -------------------
3. 実践的な応用例 2: ストリームを使用して実行可能な電子グリーティング カードを作成する
電子グリーティング カード作成ソフトウェアでは、自分で写真を選択すると、EXE 実行可能ファイルが生成されることがよくあります。グリーティングカードを開くと、音楽を再生しながら写真が表示されます。ストリーム操作を学習したので、ストリーム操作を作成することもできます。
画像を追加するプロセスでは、前の Cjt_AddtoFile を直接使用できます。ここで行う必要があるのは、画像を読み出して表示する方法です。以前の Cjt_LoadFromFile を使用して、最初に画像を読み出し、それをファイルとして保存してから読み込むことができます。ただし、より簡単な方法は、ファイル ストリームを直接読み取って、ストリームという強力なツールを使用して表示することです。 、すべてがシンプルになります。
現在最も人気のある写真は BMP 形式と JPG 形式です。これら 2 種類の画像の読み取り関数と表示関数を作成します。
関数 Cjt_BmpLoad(ImgBmp:TImage;SourceFile:String):Boolean;
変数
ソース:TFileStream;
MyFileSize:整数;
始める
Source:=TFileStream.Create(SourceFile,fmOpenRead または fmShareDenyNone);
試す
試す
Source.Seek(-sizeof(MyFileSize),soFromEnd);
Source.ReadBuffer(MyFileSize,sizeof(MyFileSize));// リソースの読み取り
Source.Seek(-MyFileSize,soFromEnd);//リソースの開始位置を特定します
ImgBmp.Picture.Bitmap.LoadFromStream(ソース);
ついに
出典.無料;
終わり;
を除外する
結果:=偽;
出口;
終わり;
結果:=真;
終わり;
上はBMP画像を読み込む関数、以下はJPG画像を読み込む関数です。 JPG単位を使用するため、プログラムにuses jpegの文を追加する必要があります。
関数 Cjt_JpgLoad(JpgImg:Timage;SourceFile:String):Boolean;
変数
ソース:TFileStream;
MyFileSize:整数;
Myjpg: TJpegImage;
始める
試す
Myjpg:= TJpegImage.Create;
Source:=TFileStream.Create(SourceFile,fmOpenRead または fmShareDenyNone);
試す
Source.Seek(-sizeof(MyFileSize),soFromEnd);
Source.ReadBuffer(MyFileSize,sizeof(MyFileSize));
Source.Seek(-MyFileSize,soFromEnd);
Myjpg.LoadFromStream(ソース);
JpgImg.Picture.Bitmap.Assign(Myjpg);
ついに
出典.無料;
Myjpg.無料;
終わり;
を除外する
結果:=false;
出口;
終わり;
結果:= true;
終わり;
この 2 つの関数を使用して、読み出しプログラムを作成できます。 BMP 画像を例に挙げてみましょう。
Delphi を実行し、新しいプロジェクトを作成し、画像表示コントロール Image1 を配置します。ウィンドウの Create イベントに次の文を書き込むだけです。
Cjt_BmpLoad(画像1,アプリケーション.Exe名);
これはヘッダー ファイルであり、前述の方法を使用して head.res リソース ファイルを生成します。
これで、アドオン プログラムの作成を開始できます。コード全体は次のとおりです。
ユニットユニット1;
インタフェース
用途
ウィンドウ、メッセージ、SysUtils、クラス、グラフィックス、コントロール、フォーム、ダイアログ、
ExtCtrls、StdCtrls、ExtDlgs;
タイプ
TForm1 = クラス(TForm)
編集1: TEdit;
ボタン 1: T ボタン;
ボタン 2: T ボタン;
OpenPictureDialog1: TOpenPictureDialog;
プロシージャ FormCreate(Sender: TObject);
プロシージャ Button1Click(送信者: TObject);
プロシージャ Button2Click(送信者: TObject);
プライベート
関数 ExtractRes(ResType, ResName, ResNewName : String):boolean;
関数 Cjt_AddtoFile(SourceFile,TargetFile:string):Boolean;
{プライベート宣言}
公共
{公的宣言}
終わり;
変数
フォーム1: TForm1;
実装
{$R *.DFM}
関数 TForm1.ExtractRes(ResType, ResName, ResNewName : String):boolean;
変数
解像度 : TResourceStream;
始める
試す
Res := TResourceStream.Create(Hinstance, Resname, Pchar(ResType));
試す
Res.SavetoFile(ResNewName);
結果:= true;
ついに
解像度無料。
終わり;
を除外する
結果:=false;
終わり;
終わり;
関数 TForm1.Cjt_AddtoFile(SourceFile,TargetFile:string):Boolean;
変数
ターゲット、ソース:TFileStream;
MyFileSize:整数;
始める
試す
Source:=TFileStream.Create(SourceFile,fmOpenRead または fmShareExclusive);
Target:=TFileStream.Create(TargetFile、fmOpenWrite または fmShareExclusive);
試す
Target.Seek(0,soFromEnd);//リソースを最後に追加します
Target.CopyFrom(ソース,0);
MyFileSize:=Source.Size+Sizeof(MyFileSize);//リソース サイズを計算し、補助プロセスの最後に書き込みます。
Target.WriteBuffer(MyFileSize,sizeof(MyFileSize));
ついに
ターゲット。無料。
出典.無料;
終わり;
を除外する
結果:=偽;
出口;
終わり;
結果:=真;
終わり;
プロシージャ TForm1.FormCreate(送信者: TObject);
始める
Caption:='Bmp2Exe デモ プログラム 著者: Chen Jingtao';
Edit1.Text:='';
OpenPictureDialog1.DefaultExt := GraphicExtension(TBitmap);
OpenPictureDialog1.Filter := GraphicFilter(TBitmap);
Button1.Caption:='BMP 画像を選択';
Button2.Caption:='EXE を生成';
終わり;
プロシージャ TForm1.Button1Click(送信者: TObject);
始める
OpenPictureDialog1.Execute の場合
Edit1.Text:=OpenPictureDialog1.FileName;
終わり;
プロシージャ TForm1.Button2Click(送信者: TObject);
変数
HeadTemp:文字列;
始める
ファイルが存在しない場合(Edit1.Text)
始める
Application.MessageBox('BMP 画像ファイルが存在しません。もう一度選択してください!','メッセージ',MB_ICONINFORMATION+MB_OK)
出口;
終わり;
HeadTemp:=ChangeFileExt(Edit1.Text,'.exe');
if ExtractRes('exefile','head',HeadTemp) then
if Cjt_AddtoFile(Edit1.Text,HeadTemp) then
Application.MessageBox('EXE ファイルが正常に生成されました!','メッセージ',MB_ICONINFORMATION+MB_OK)
それ以外
始める
FileExists(HeadTemp) の場合は、DeleteFile(HeadTemp);
Application.MessageBox('EXE ファイルの生成に失敗しました!','メッセージ',MB_ICONINFORMATION+MB_OK)
終わり;
終わり;
終わり。
どうでしょうか?すごいですね:) プログラムのインターフェースをより美しくし、いくつかの機能を追加すると、登録が必要なソフトウェアとそれほど遜色がないことがわかります。
-------------------------------------------------- ------------------------
実践的な応用例 3: ストリームを使用して独自の OICQ を作成する
OICQ は、深センテンセント社が開発したオンラインリアルタイム通信ソフトウェアで、中国に大規模なユーザーベースを持っています。ただし、OICQ を使用するには、インターネットに接続し、Tencent のサーバーにログインする必要があります。したがって、自分で作成してローカル ネットワークで使用できます。
OICQ は、コネクションレス型プロトコルである UDP プロトコルを使用します。つまり、通信相手がコネクションを確立せずに情報を送信できるため、効率が比較的高くなります。 Delphi 自体に付属する FastNEt の NMUDP コントロールは、UDP プロトコルのユーザー データグラム コントロールです。ただし、TNMXXX コントロールにはバグがあるため、このコントロールを使用する場合は、コンピュータをシャットダウンする前にプログラムを終了する必要があることに注意してください。すべての nm コントロールの基礎である PowerSocket によって使用される ThreadTimer は、欠陥に対処するために隠しウィンドウ (クラス TmrWindowClass) を使用します。
何が問題だったのか:
Psock::TThreadTimer::WndProc(var msg:TMessage)
msg.message=WM_TIMER の場合
彼は自分でそれを処理します
msg.結果:=0
それ以外
msg.result:=DefWindowProc(0,....)
終わり
問題は、DefWindowProc を呼び出すときに送信される HWND パラメータが実際には定数 0 であるため、実際には DefWindowProc を呼び出すと、WM_QUERYENDsession を含む入力メッセージが 0 を返し、ウィンドウが終了できないことです。 DefWindowProc の異常な呼び出しにより、実際には、WM_TIMER を除いて、DefWindowProc によって処理される他のメッセージは無効になります。
解決策は PSock.pas にあります
TThreadTimer.Wndproc 内
結果 := DefWindowProc( 0, Msg, WPARAM, LPARAM );
次のように変更します。
結果 := DefWindowProc( FWindowHandle, Msg, WPARAM, LPARAM );
OICQ の初期の低レベル バージョンにもこの問題がありました。OICQ がオフになっていない場合、画面が一瞬点滅し、コンピュータの電源を切ると元に戻ります。
さて、さっそく OICQ を書いてみましょう。これは実際に Delphi に付属する例です:)。
新しいプロジェクトを作成し、FASTNET パネルからウィンドウに NMUDP コントロールをドラッグし、Editip、EditPort、EditMyTxt という名前の 3 つの EDIT、3 つのボタン BtSend、BtClear、BtSave、MEMOMemoReceive、SaveDialog、およびステータス バーを配置します。ユーザーが BtSend をクリックすると、メモリ ストリーム オブジェクトが作成され、送信するテキスト情報がメモリ ストリームに書き込まれ、NMUDP がストリームを送信します。 NMUDP がデータを受信すると、DataReceived イベントがトリガーされます。ここでは、受信したストリームを文字情報に変換して表示します。
注: ストリーム オブジェクトを作成して使用した後は、必ずすべてのストリーム オブジェクトを解放 (Free) する必要があります。ただし、ストリームの作成に失敗した場合、プログラムは例外を生成します。ストリームが正常に確立されていない場合は、確立された場合にのみ解放されるため、Free を使用する方が安全です。
このプログラムでは、いくつかの重要なプロパティを持つ NMUDP コントロールを使用します。 RemoteHost はリモート コンピューターの IP またはコンピューター名を表し、LocalPort は主に受信データがあるかどうかを監視するローカル ポートです。 RemotePort はリモート ポートであり、データ送信時にはこのポートを介してデータが送信されます。これらを理解すれば、私たちのプログラムはすでに理解できます。
コード全体は次のとおりです。
ユニットユニット1;
インタフェース
用途
ウィンドウ、メッセージ、SysUtils、クラス、グラフィックス、コントロール、フォーム、ダイアログ、StdCtrls、ComCtrls、NMUDP;
タイプ
TForm1 = クラス(TForm)
NMUDP1: TNMUDP;
編集IP: TEdit;
編集ポート: TEdit;
EditMyTxt: TEdit;
メモ受信: Tメモ;
BtSend: TButton;
BtClear: Tボタン;
BtSave: Tボタン;
ステータスバー1: Tステータスバー;
SaveDialog1: TSaveDialog;
プロシージャ BtSendClick(送信者: TObject);
プロシージャ NMUDP1DataReceived(送信者: TComponent; NumberBytes: 整数;
FromIP: 文字列; ポート: 整数);
プロシージャ NMUDP1InvalidHost(処理される変数: ブール値);
プロシージャ NMUDP1DataSend(送信者: TObject);
プロシージャ FormCreate(Sender: TObject);
プロシージャ BtClearClick(送信者: TObject);
プロシージャ BtSaveClick(送信者: TObject);
プロシージャ EditMyTxtKeyPress(Sender: TObject; var Key: Char);
プライベート
{プライベート宣言}
公共
{公的宣言}
終わり;
変数
フォーム1: TForm1;
実装
{$R *.DFM}
プロシージャ TForm1.BtSendClick(送信者: TObject);
変数
MyStream: TMemoryStream;
MySendTxt: 文字列;
インポート、icode:整数;
始める
Val(EditPort.Text,Iport,icode);
icode<>0 の場合
始める
Application.MessageBox('ポートは番号である必要があります。再入力してください!','メッセージ',MB_ICONINFORMATION+MB_OK);
出口;
終わり;
NMUDP1.RemoteHost := EditIP.Text;
NMUDP1.LocalPort:=Iport {ローカルポート}
NMUDP1.RemotePort := {リモート ポート}
MySendTxt := EditMyTxt.Text;
MyStream := TMemoryStream.Create {ストリームの作成}
試す
MyStream.Write(MySendTxt[1], Length(EditMyTxt.Text));{データの書き込み}
NMUDP1.SendStream(MyStream); {ストリームを送信}
ついに
MyStream.Free {リリースストリーム}
終わり;
終わり;
プロシージャ TForm1.NMUDP1DataReceived(送信者: TComponent;
NumberBytes: 整数; FromIP: 文字列; ポート: 整数);
変数
MyStream: TMemoryStream;
MyReciveTxt: 文字列;
始める
MyStream := TMemoryStream.Create {ストリームの作成}
試す
NMUDP1.ReadStream(MyStream);{ストリームを受信}
SetLength(MyReciveTxt,NumberBytes);{NumberBytes は受信したバイト数です}
MyStream.Read(MyReciveTxt[1],NumberBytes);{データの読み取り}
MemoReceive.Lines.Add('ホスト '+FromIP+':'+MyReciveTxt から情報を受信しました);
ついに
MyStream.Free {リリースストリーム}
終わり;
終わり;
プロシージャ TForm1.NMUDP1InvalidHost(処理される変数: ブール値);
始める
Application.MessageBox('相手のIPアドレスが間違っています。再入力してください!','メッセージ',MB_ICONINFORMATION+MB_OK);
終わり;
プロシージャ TForm1.NMUDP1DataSend(送信者: TObject);
始める
StatusBar1.SimpleText:='メッセージは正常に送信されました!';
終わり;
プロシージャ TForm1.FormCreate(送信者: TObject);
始める
EditIP.Text:='127.0.0.1';
EditPort.Text:='8868';
BtSend.Caption:='送信';
BtClear.Caption:='チャット履歴をクリア';
BtSave.Caption:='チャット履歴を保存';
MemoReceive.ScrollBars:=ss Both;
メモ受信.クリア;
EditMyTxt.Text:='ここに情報を入力し、[送信] をクリックしてください。';
StatusBar1.SimplePanel:=true;
終わり;
プロシージャ TForm1.BtClearClick(送信者: TObject);
始める
メモ受信.クリア;
終わり;
プロシージャ TForm1.BtSaveClick(送信者: TObject);
始める
SaveDialog1.Execute の場合、MemoReceive.Lines.SaveToFile(SaveDialog1.FileName);
終わり;
プロシージャ TForm1.EditMyTxtKeyPress(Sender: TObject; var Key: Char);
始める
Key=#13 の場合、BtSend.Click;
終わり;
終わり。
OICQ は Socket5 通信方式を使用しているため、上記のプログラムは OICQ よりもはるかに遅れています。オンラインになると、まずサーバーから友達情報とオンラインステータスを取得し、送信がタイムアウトした場合は、まずサーバーに情報を保存し、次回相手がオンラインになるのを待ってから送信し、その後、メッセージを削除します。サーバーのバックアップ。たとえば、送信された情報を最初に AND または演算用の ASCII コードに変換し、受信側が判断するヘッダー情報を追加するなど、以前に学んだ概念に基づいてこのプログラムを改良できます。情報を受信すると、情報ヘッダーが正しいかどうかに関係なく、情報が正しい場合にのみ復号化されて表示されるため、セキュリティと機密性が向上します。
さらに、UDP プロトコルのもう 1 つの大きな利点は、ブロードキャストできることです。これは、同じネットワーク セグメント内の誰もが特定の IP アドレスを指定せずに情報を受信できることを意味します。ネットワーク セグメントは通常、A、B、C の 3 つのカテゴリに分類されます。
1~126.XXX.XXX.XXX (クラス A ネットワーク): ブロードキャスト アドレスは XXX.255.255.255 です。
128~191.XXX.XXX.XXX (クラス B ネットワーク): ブロードキャスト アドレスは XXX.XXX.255.255 です。
192~254.XXX.XXX.XXX(カテゴリーCネットワーク):ブロードキャストアドレスはXXX.XXX.XXX.255です。
たとえば、3 台のコンピュータが 192.168.0.1、192.168.0.10、192.168.0.18 である場合、情報を送信する場合、IP アドレス 192.168.0.255 を指定するだけでブロードキャストが実現されます。以下は、IP をブロードキャスト IP に変換する関数です。これを使用して、独自の OICQ を改善します^-^。
関数 Trun_ip(S:string):string;
var s1、s2、s3、ss、sss、ヘッド:文字列;
n、m:整数;
始める
sss:=S;
n:=pos('.',s);
s1:=コピー(s,1,n);
m:=長さ(s1);
削除(s,1,m);
Head:=copy(s1,1,(length(s1)-1));
n:=pos('.',s);
s2:=コピー(s,1,n);
m:=長さ(s2);
削除(s,1,m);
n:=pos('.',s);
s3:=コピー(s,1,n);
m:=長さ(s3);
削除(s,1,m);
ss:=sss;
if strtoint(Head) in [1..126] then ss:=s1+'255.255.255' //1~126.255.255.255 (クラス A ネットワーク)
if strtoint(Head) in [128..191] then ss:=s1+s2+'255.255';//128~191.XXX.255.255 (クラス B ネットワーク)
if strtoint(Head) in [192..254] then ss:=s1+s2+s3+'255'; //192~254.XXX.XXX.255 (カテゴリネットワーク)
結果:=ss;
終わり;
-------------------------------------------------- ------------------------
5. 応用例 4: ストリームを使用してネットワーク上に画面イメージを送信する
多くのネットワーク管理プログラムを見たことがあるはずです。そのようなプログラムの機能の 1 つは、リモート コンピューターの画面を監視することです。実際、これはストリーム操作を使用しても実現されます。以下に例を示します。この例は 2 つのプログラムに分かれており、1 つはサーバー、もう 1 つはクライアントです。プログラムをコンパイルした後は、単一のマシン、ローカル ネットワーク、またはインターネット上で直接使用できます。対応するコメントが番組内に記載されています。詳細な分析は後ほど行います。
新しいプロジェクトを作成し、ServerSocket コントロールを [インターネット] パネルのウィンドウにドラッグします。このコントロールは主にクライアントを監視し、クライアントとの接続と通信を確立するために使用されます。リスニング ポートを設定した後、メソッド Open または Active:=True を呼び出して作業を開始します。注: 以前の NMUDP とは異なり、ソケットがリッスンを開始すると、そのポートを変更することはできません。変更する場合は、まず Close を呼び出すか、Active を False に設定する必要があります。そうしないと、例外が発生します。また、ポートがすでに開いている場合、そのポートは使用できなくなります。したがって、プログラムを終了する前にプログラムを再度実行することはできません。そうしないと、例外が発生し、エラー ウィンドウが表示されます。実際のアプリケーションでは、プログラムが実行されているかどうかを判断し、すでに実行されている場合は終了することでエラーを回避できます。
クライアントからデータが渡されると、ServerSocket1ClientRead イベントがトリガーされ、受信したデータをここで処理できます。このプログラムでは、主にクライアントから送信された文字情報を受信し、事前の合意に従って対応する動作を実行します。
全体のプログラムコードは次のとおりです。
ユニット Unit1;{サーバー プログラム}
インタフェース
用途
ウィンドウ、メッセージ、SysUtils、クラス、グラフィックス、コントロール、フォーム、ダイアログ、JPEG、ExtCtrls、ScktComp。
タイプ
TForm1 = クラス(TForm)
ServerSocket1: TServerSocket;
プロシージャ ServerSocket1ClientRead(送信者: TObject;ソケット: TCustomWinSocket);
プロシージャ FormCreate(Sender: TObject);
プロシージャ FormClose(Sender: TObject; var Action: TCloseAction);
プライベート
プロシージャ Cjt_GetScreen(var Mybmp: TBitmap; DrawCur: Boolean);
{カスタマイズされた画面キャプチャ機能、DrawCur はマウス画像をキャプチャするかどうかを示します}
{プライベート宣言}
公共
{公的宣言}
終わり;
変数
フォーム1: TForm1;
MyStream: TMemorystream;{メモリ ストリーム オブジェクト}
実装
{$R *.DFM}
プロシージャ TForm1.Cjt_GetScreen(var Mybmp: TBitmap; DrawCur: Boolean);
変数
Cursorx、Cursory: 整数;
DC:HDC;
マイカン: Tキャンバス;
R: TRect;
描画位置: TPoint;
MyCursor: TIcon;
ホールド: hwnd;
スレッドID: dword;
mp: tポイント;
pIconInfo: TIconInfo;
始める
Mybmp := Tbitmap.Create {BMPMAP の作成}
Mycan := TCanvas.Create {画面キャプチャ}
dc := GetWindowDC(0);
試す
Mycan.ハンドル := dc;
R := Rect(0, 0, screen.Width, screen.Height);
Mybmp.Width := R.Right;
Mybmp.Height := R.Bottom;
Mybmp.Canvas.CopyRect(R, Mycan, R);
ついに
releaseDC(0, DC);
終わり;
Mycan.ハンドル := 0;
Mycan.無料。
if DrawCur then {マウス画像を描画する}
始める
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);
カーソルx := DrawPos.x - ラウンド(pIconInfo.xHotspot);
カーソル := DrawPos.y -round(pIconInfo.yHotspot);
Mybmp.Canvas.Draw(cursorx, Cursory, MyCursor); {マウスで描画}
DeleteObject(pIconInfo.hbmColor);{GetIconInfo は、使用時に 2 つのビットマップ オブジェクトを作成します。これらの 2 つのオブジェクトは手動で解放する必要があります。}
DeleteObject(pIconInfo.hbmMask); {それ以外の場合は、呼び出し後にビットマップが作成され、リソースが枯渇するまで複数の呼び出しで複数のビットマップが生成されます}
Mycursor.ReleaseHandle {配列メモリを解放}
MyCursor.Free {マウス ポインタを放す}
終わり;
終わり;
プロシージャ TForm1.FormCreate(送信者: TObject);
始める
サーバーソケット1.ポート:= 3000;
ServerSocket1.Open {ソケットがリッスンを開始します}
終わり;
プロシージャ TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
始める
ServerSocket1.Active の場合、ServerSocket1.Close {ソケットを閉じる}
終わり;
プロシージャ TForm1.ServerSocket1ClientRead(送信者: TObject;
ソケット: TCustomWinSocket);
変数
S、S1: 文字列。
MyBmp: TBitmap;
Myjpg: TJpegimage;
始める
S := ソケット.受信テキスト;
if S = 'cap' then {クライアントは画面キャプチャ コマンドを発行します}
始める
試す
MyStream := TMemorystream.Create;{メモリ ストリームの作成}
MyBmp := TBitmap.Create;
Myjpg := TJpegimage.Create;
Cjt_GetScreen(MyBmp, True); {True はマウス画像を取得することを意味します}
Myjpg.Assign(MyBmp); {インターネット上で送信しやすいように BMP 画像を JPG 形式に変換します}
Myjpg.CompressionQuality := 10; {JPG ファイルの圧縮率の設定、数値が大きいほど画像は鮮明になりますが、データは大きくなります}
Myjpg.SaveToStream(MyStream); {JPG 画像をストリームに書き込む}
Myjpg.無料;
MyStream.Position := 0;{注: この文は必ず追加してください}
s1 := inttostr(MyStream.size);{ストリームのサイズ}
Socket.sendtext(s1); {送信ストリーム サイズ}
ついに
MyBmp.無料;
終わり;
終わり;
if s = 'ready' then {クライアントは画像を受信する準備ができています}
始める
MyStream.Position := 0;
Socket.SendStream(MyStream); {ストリームを送信する}
終わり;
終わり;
終わり。
上がサーバーなので、以下にクライアントプログラムを書きましょう。新しいプロジェクトを作成し、ソケット コントロール ClientSocket、画像表示コントロール Image、Panel、Edit、2 つの Button、およびステータス バー コントロール StatusBar1 を追加します。注: Edit1 と 2 つのボタンをPanel1 の上に配置します。 ClientSocket の属性は ServerSocket に似ていますが、接続するサーバーの IP アドレスを示す追加の Address 属性があります。 IP アドレスを入力したら、「接続」をクリックしてサーバー プログラムとの接続を確立します。成功すると、通信が開始されます。 「画面キャプチャ」をクリックすると文字がサーバーに送信されます。このプログラムでは JPEG 画像ユニットを使用するため、Uses に Jpeg を追加する必要があります。
コード全体は次のとおりです。
ユニット Unit2{クライアント};
インタフェース
用途
Windows、メッセージ、SysUtils、クラス、グラフィックス、コントロール、フォーム、ダイアログ、StdCtrls、ScktComp、ExtCtrls、Jpeg、ComCtrls;
タイプ
TForm1 = クラス(TForm)
ClientSocket1: TClientSocket;
画像1: T画像;
ステータスバー1: Tステータスバー;
パネル1: Tパネル;
編集1: TEdit;
ボタン 1: T ボタン;
ボタン 2: T ボタン;
プロシージャ Button1Click(送信者: TObject);
プロシージャ ClientSocket1Connect(送信者: TObject;
ソケット: TCustomWinSocket);
プロシージャ Button2Click(送信者: TObject);
プロシージャ ClientSocket1Error(送信者: TObject; ソケット: TCustomWinSocket;
エラーイベント: TErrorEvent; var エラーコード: 整数);
プロシージャ ClientSocket1Read(送信者: TObject; ソケット: TCustomWinSocket);
プロシージャ FormCreate(Sender: TObject);
プロシージャ FormClose(Sender: TObject; var Action: TCloseAction);
プロシージャ ClientSocket1Disconnect(送信者: TObject;
ソケット: TCustomWinSocket);
プライベート
{プライベート宣言}
公共
{公的宣言}
終わり;
変数
フォーム1: TForm1;
MySize: 倍長整数;
MyStream: TMemorystream;{メモリ ストリーム オブジェクト}
実装
{$R *.DFM}
プロシージャ TForm1.FormCreate(送信者: TObject);
始める
{---------- 以下はウィンドウ コントロールの外観プロパティを設定します ------------- }
{注: Button1、Button2、Edit1 をPanel1 の上に配置します}
Edit1.Text := '127.0.0.1';
Button1.Caption := 'ホストに接続';
Button2.Caption := '画面キャプチャ';
Button2.Enabled := false;
パネル 1.Align := alTop;
Image1.Align := alClient;
Image1.Stretch := True;
StatusBar1.Align:=alBottom;
StatusBar1.SimplePanel := True;
{------------------------------------------------ ---------}
MyStream := TMemorystream.Create {メモリ ストリーム オブジェクトの作成}
MySize := 0;
終わり;
プロシージャ TForm1.Button1Click(送信者: TObject);
始める
ClientSocket1.Active でない場合は、
始める
ClientSocket1.Address := Edit1.Text {リモート IP アドレス}
ClientSocket1.Port := 3000;
ClientSocket1.Open {接続を確立}
終わり;
終わり;
プロシージャ TForm1.Button2Click(送信者: TObject);
始める
Clientsocket1.Socket.SendText('cap'); {サーバーに画面イメージをキャプチャするように通知するコマンドを送信します}
Button2.Enabled := False;
終わり;
プロシージャ TForm1.ClientSocket1Connect(送信者: TObject;
ソケット: TCustomWinSocket);
始める
StatusBar1.SimpleText := 'ホストあり' + ClientSocket1.Address + '接続が正常に確立されました!';
Button2.Enabled := True;
終わり;
プロシージャ TForm1.ClientSocket1Error(送信者: TObject;
ソケット: TCustomWinSocket; エラーイベント: TErrorEvent;
varErrorCode: 整数);
始める
エラーコード := 0; {エラー ウィンドウをポップアップ表示しない}
StatusBar1.SimpleText := 'ホストに接続できません' + ClientSocket1.Address + '接続が確立されました!';
終わり;
プロシージャ TForm1.ClientSocket1Disconnect(送信者: TObject;
ソケット: TCustomWinSocket);
始める
StatusBar1.SimpleText := 'ホストあり' + ClientSocket1.Address + '切断!';
Button2.Enabled := False;
終わり;
プロシージャ TForm1.ClientSocket1Read(送信者: TObject;
ソケット: TCustomWinSocket);
変数
MyBuffer: バイトの配列[0..10000] {受信バッファを設定}
MyReceviceLength: 整数;
S: 文字列。
MyBmp: TBitmap;
MyJpg: TJpegimage;
始める
StatusBar1.SimpleText := 'データを受信中...';
if MySize = 0 then {MySize はサーバーが送信したバイト数です。0 の場合は、画像の受信がまだ開始されていないことを意味します。
始める
S := ソケット.受信テキスト;
MySize := Strtoint(S); {受信するバイト数を設定します}
Clientsocket1.Socket.SendText('ready'); {サーバーに画像の送信開始を通知するコマンドを送信します}
終わり
それ以外
begin {以下は画像データ受信部分です}
MyReceviceLength := ソケット.ReceiveLength {読み取りパケット長}
StatusBar1.SimpleText := 'データを受信しています。データ サイズは次のとおりです:' + inttostr(MySize);
Socket.ReceiveBuf(MyBuffer, MyReceviceLength); {データ パケットを受信し、バッファに読み込みます}
MyStream.Write(MyBuffer, MyReceviceLength); {ストリームにデータを書き込みます}
if MyStream.Size >= MySize then {ストリーム長が受信バイト数より大きければ受信完了}
始める
MyStream.Position := 0;
MyBmp := tbitmap.Create;
MyJpg := tjpegimage.Create;
試す
MyJpg.LoadFromStream(MyStream); {ストリーム内のデータを JPG 画像オブジェクトに読み込みます}
MyBmp.Assign(MyJpg); {JPG を BMP に変換}
StatusBar1.SimpleText := '画像を表示中';
Image1.Picture.Bitmap.Assign(MyBmp); {image1 要素に割り当てられます}
最後に {以下はクリーンアップ作業です}
MyBmp.無料;
MyJpg.無料;
Button2.Enabled := true;
{Socket.SendText('cap');画面を連続的にキャプチャするにはこの文を追加します}
MyStream.Clear;
私のサイズ := 0;
終わり;
終わり;
終わり;
終わり;
プロシージャ TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
始める
MyStream.Free {メモリ ストリーム オブジェクトを解放する}
ClientSocket1.Active の場合、ClientSocket1.Close {ソケット接続を閉じる}
終わり;
終わり。
プログラムの原則: サーバーを実行してリスニングを開始し、次にクライアントを実行し、サーバーの IP アドレスを入力して接続を確立し、文字を送信してサーバーに画面をキャプチャするよう通知します。サーバーはカスタム関数 Cjt_GetScreen を呼び出して画面をキャプチャし、BMP として保存し、BMP を JPG に変換し、JPG をメモリ ストリームに書き込み、ストリームをクライアントに送信します。ストリームを受信した後、クライアントは逆の操作を実行し、ストリームを JPG に変換し、次に BMP に変換して表示します。
注: Socket の制限により、大きすぎるデータを一度に送信することはできませんが、送信できるのは数回のみです。したがって、プログラムでは、画面キャプチャをストリームに変換した後、サーバーは最初にストリームのサイズを送信し、クライアントはこの数値を使用してストリームが受信されたかどうかを判断します。そうです、変換されて表示されます。
このプログラムと以前の自作 OICQ は両方ともメモリ ストリーム オブジェクト TMemoryStream を使用します。実際、このストリーム オブジェクトはプログラミングで最も一般的に使用され、I/O の読み取りおよび書き込み機能を向上させることができ、複数の異なる種類のストリームを同時に操作して相互にデータを交換する場合は、これを使用します。 「仲介者」それは最高です。たとえば、ストリームを圧縮または解凍する場合は、まず TMemoryStream オブジェクトを作成し、次に他のデータをそのオブジェクトにコピーして、対応する操作を実行します。メモリ内で直接動作するため、効率が非常に高くなります。場合によっては遅延にさえ気づかないこともあります。
プログラムの改善点: もちろん、送信前に圧縮してから送信する圧縮ユニットを追加することもできます。注: ここには、BMP を JPG に変換するのではなく、直接圧縮してから圧縮するというトリックもあります。実験によると、上記のプログラムの画像のサイズは約 40 ~ 50 KB ですが、LAH 圧縮アルゴリズムを使用して処理すると、わずか 8 ~ 12 KB になり、送信が高速になります。より速く処理したい場合は、この方法を使用できます。最初に最初の画像をキャプチャして送信し、次に 2 番目の画像から開始して、前の画像とは異なる領域の画像のみを送信します。この方法を使用する Remote Administrator と呼ばれる外部プログラムがあります。彼らがテストしたデータは次のとおりです。ローカル ネットワークでは 1 秒あたり 100 ~ 500 フレーム、ネットワーク速度が非常に遅い場合、インターネットでは 1 秒あたり 5 ~ 10 フレームです。これらの脱線は、1 つの真実を説明したいだけです。問題を考えるとき、特にプログラムを作成するとき、特に非常に複雑に見えるプログラムを書くときは、その問題に囚われすぎないでください。別の角度から考えたほうがよい場合もあります。 。プログラムは死んだ、才能は生きている。もちろん、これらは経験の蓄積に頼るしかありません。しかし、最初から良い習慣を身につけることは、あなたの人生を通して恩恵をもたらします。