DELPHI を使用してソフトウェアを開発する過程では、私たちは草原で幸せな牛や羊の群れのようなもので、Object Pascal 言語によってもたらされる太陽の光と、さまざまな VCL コントロールによって提供される豊かな水生植物を気楽に楽しんでいます。限りなく青い空を見上げ、地上の緑豊かな草を見下ろしながら、宇宙の大きさや、分子や原子より小さいものは何なのか、誰が考えるでしょうか。それは哲学者にとっての問題です。このとき、哲学者は高い山の頂上に座って、宇宙の星雲の変化を見上げ、地面を這う昆虫を見つめていたが、突然振り返って、私たちの草食動物の群れにうなずき、微笑んだ。牛と羊。彼は一片の草を拾い上げ、そっと口に含み、目を閉じて注意深く味わった。哲学者の口の中のこの草の味は何だったのだろうか。しかし、その顔にはいつも満足そうな笑みが浮かんでいた。
DELPHI の微視的な原子の世界を知り、理解することで、DELPHI の巨視的なアプリケーション構造を完全に理解できるようになり、それにより、より広いイデオロギー空間でソフトウェアを開発できるようになります。ニュートンが巨視的物体の運動を発見したものの、なぜ物体がそのように動くのか理解できずに悩んだのと同じで、逆にアインシュタインは基本粒子の法則と巨視的物体の運動との幸福な相対性理論を体験したのと同じである。 !
セクション 1 TObject アトム
Tオブジェクトとは何ですか?
これは、Object Pascal 言語アーキテクチャの基本コアであり、さまざまな VCL コントロールの起源です。 TObject は、DELPHI アプリケーションを構成するアトムの 1 つと考えることができます。もちろん、それらは基本的な Pascal 構文要素などのより微細な要素で構成されています。
TObject は DELPHI コンパイラによって内部的にサポートされているため、TObject は DELPHI プログラムのアトムであると言われています。 TObject を祖先クラスとして指定しない場合でも、すべてのオブジェクト クラスは TObject から派生します。 TObject は、システムの一部である System ユニットで定義されます。 System.pas ユニットの先頭に、次のコメント テキストがあります。
{P再定義された定数、型、プロシージャ、}
{ および関数 (True、Integer、} など)
{Writeln) には実際の宣言がありません。}
{代わりにコンパイラに組み込まれています}
{ と は宣言されたものとして扱われます }
{ システムユニットの先頭にある }
これは、このユニットに事前定義された定数、型、プロシージャ、関数 (True、Integer、Writeln など) が含まれていることを意味します。これらは実際には宣言されていませんが、コンパイラによって組み込まれており、コンパイルの開始時に使用されると考えられます。明示された定義であること。 Classes.pas や Windows.pas などの他のソース プログラム ファイルをプロジェクト ファイルに追加して、ソース コードをコンパイルおよびデバッグすることはできますが、System.pas ソース プログラム ファイルをコンパイルのためにプロジェクト ファイルに追加することは絶対にできません。 DELPHI は、System! の重複定義についてコンパイル エラーを報告します。
したがって、TObject はコンパイラによって内部的に提供される定義です。DELPHI を使用してプログラムを開発する人にとって、TObject はアトミックなものです。
System ユニット内の TObject の定義は次のとおりです。
TObject = クラス
コンストラクター作成;
手続き無料。
クラス関数 InitInstance(インスタンス: ポインター): TObject;
プロシージャ CleanupInstance;
関数クラスタイプ: TClass;
クラス関数 ClassName: ShortString;
クラス関数 ClassNameIs(const Name: string): ブール値;
クラス関数 ClassParent: TClass;
クラス関数 ClassInfo: ポインタ;
クラス関数 InstanceSize: 倍長整数;
クラス関数 InheritsFrom(AClass: TClass): ブール値;
クラス関数 MethodAddress(const Name: ShortString): ポインタ;
クラス関数 MethodName(アドレス: ポインタ): ShortString;
関数 FieldAddress(const Name: ShortString): ポインタ;
function GetInterface(const IID: TGUID; out Obj): ブール値;
クラス関数 GetInterfaceEntry(const IID: TGUID): PInterfaceEntry;
クラス関数 GetInterfaceTable: PInterfaceTable;
関数 SafeCallException(ExceptObject: TObject;
ExceptAddr: ポインタ): HResult;
仮想プロシージャ;
プロシージャ BeforeDestruction;
プロシージャ Dispatch(var Message);
プロシージャ DefaultHandler(var Message);
クラス関数 NewInstance: TObject 仮想;
プロシージャ FreeInstance;
デストラクター 仮想を破棄します。
終わり;
次に、TObject アトムのドアを徐々にノックして、内部にどのような構造があるかを確認します。
TObject がすべてのオブジェクトの基本クラスであることはわかっていますが、オブジェクトとは正確には何でしょうか?
DELPHI のオブジェクトはすべてポインタであり、メモリ内でオブジェクトが占める領域を示します。オブジェクトはポインターですが、オブジェクトのメンバーを参照する場合、コード MyObject^.GetName を記述する必要はなく、MyObject.GetName のみを記述できます。これは Object Pascal 言語の拡張構文であり、次のようになります。コンパイラによってサポートされています。 C++ Builder のオブジェクトはポインターとして定義する必要があるため、C++ Builder を使用する友人はオブジェクトとポインターの関係について非常に明確です。オブジェクトポインタが指す場所がオブジェクトがデータを格納するオブジェクト空間です。オブジェクトポインタが指すメモリ空間のデータ構造を解析してみましょう。
オブジェクト空間の最初の 4 バイトは、オブジェクト クラスの仮想メソッド アドレス テーブル (VMT – 仮想メソッド テーブル) を指します。次の空間は、オブジェクト自身のメンバデータを格納する空間であり、オブジェクトの最も原始的な祖先クラスのデータメンバからオブジェクトクラスのデータメンバまでの全体の順序と、そのオブジェクトのデータメンバの順序で格納される。データ メンバーはクラスの各レベルで定義されます。
クラスの仮想メソッド テーブル (VMT) には、そのクラスの元の祖先クラスから派生したすべてのクラスの仮想メソッドのプロシージャ アドレスが保持されます。クラスの仮想メソッドは、予約語 virtual で宣言されたメソッドです。仮想メソッドは、オブジェクトの多態性を実現するための基本的なメカニズムです。予約語 Dynamic で宣言された動的メソッドもオブジェクトのポリモーフィズムを実現できますが、そのようなメソッドは仮想メソッド アドレス テーブル (VMT) に格納されません。これは、クラスのストレージ領域を節約できる Object Pascal によって提供される別のメソッドにすぎません。ただし、通話速度は犠牲になります。
クラスの仮想メソッドを自分で定義しない場合でも、クラスのオブジェクトは仮想メソッドのアドレス テーブルへのポインタを持ちますが、アドレス エントリの長さはゼロです。しかし、TObject で定義された Destroy、FreeInstance などの仮想メソッドはどこに保存されるのでしょうか?これらのメソッド アドレスは、VMT ポインタに対して負の方向にオフセットされた空間に格納されていることがわかります。実際、VMT テーブルの負の方向に 76 バイトオフセットされたデータ空間は、オブジェクト クラスのシステム データ構造です。これらのデータ構造はコンパイラに関連しており、将来の DELPHI バージョンでは変更される可能性があります。
したがって、VMT は負のオフセット アドレス空間から始まるデータ構造であると考えることができます。負のオフセット データ領域は VMT のシステム データ領域であり、VMT の正のオフセット データ領域はユーザー データ領域 (カスタマイズされた仮想方式) であると考えることができます。アドレステーブル)。 TObject で定義されるクラス情報やオブジェクト実行時情報に関連する関数や手続きは、一般に VMT のシステム データに関連します。
VMT データはクラスを表します。実際、VMT はクラスです。 Object Pascal では、TObject、TComponent などの識別子を使用してクラスを表し、DELPHI の内部でそれぞれの VMT データとして実装されます。予約語のクラスで定義されたクラスの型は、実際には関連する VMT データへのポインタです。
このアプリケーションの場合、VMT データは静的データであり、コンパイラーがアプリケーションをコンパイルした後、このデータ情報が決定され、初期化されます。私たちが作成するプログラム ステートメントは、VMT 関連の情報にアクセスしたり、オブジェクトのサイズ、クラス名、実行時属性データなどの情報を取得したり、仮想メソッドを呼び出したり、メソッドの名前やアドレスを読み取ったりすることができます。
オブジェクトが生成されると、システムはオブジェクトにメモリ空間を割り当て、オブジェクトを関連するクラスに関連付けます。したがって、オブジェクトに割り当てられたデータ空間の最初の 4 バイトは、クラス VMT データへのポインタになります。
オブジェクトがどのように生まれ、消滅するかを見てみましょう。 3歳の息子が芝生の上で飛び跳ねているのを見ていると、命の誕生の過程に立ち会ったからこそ、命の意味や偉大さが実感できます。死を経験した人だけが、生をより理解し、大切にすることができるのです。それでは、オブジェクトの生成と消滅のプロセスを理解しましょう。
次のステートメントを使用して最も単純なオブジェクトを構築できることは誰もが知っています。
AnObject := TObject.Create;
コンパイラはコンパイルを次のように実装します。
TObject に対応する VMT に基づいて、TObject の Create コンストラクターを呼び出します。 Create コンストラクターはシステムの ClassCreate プロセスを呼び出し、システムの ClassCreate プロセスは、そこに格納されているクラス VMT を通じて NewInstance 仮想メソッドを呼び出します。 NewInstance メソッドを呼び出す目的は、オブジェクトのインスタンス空間を確立することです。このメソッドはオーバーロードしていないため、これは TObject クラスの NewInstance です。 TObjec クラスの NewInstance メソッドは、GetMem プロシージャを呼び出して、VMT テーブル内のコンパイラによって初期化されたオブジェクト インスタンス サイズ (InstanceSize) に基づいてオブジェクトにメモリを割り当てた後、InitInstance メソッドを呼び出して割り当てられた領域を初期化します。 InitInstance メソッドは、まずオブジェクト空間の最初の 4 バイトをオブジェクト クラスに対応する VMT へのポインターに初期化し、次に残りの空間をクリアします。オブジェクト インスタンスを確立した後、仮想メソッド AfterConstruction も呼び出されます。最後に、オブジェクトインスタンスデータのアドレスポインタをAnObject変数に保存することで、AnObjectオブジェクトが誕生します。
同様に、次のステートメントを使用してオブジェクトを破棄できます。
AnObject.Destroy;
TObject のデストラクタである Destroy は、仮想メソッドとして宣言されます。これもシステム固有の仮想メソッドの 1 つです。 Destory メソッドは、最初に BeforeDestruction 仮想メソッドを呼び出し、次にシステムの ClassDestroy プロセスを呼び出します。 ClassDestory プロセスはクラス VMT を通じて FreeInstance 仮想メソッドを呼び出し、FreeInstance メソッドは FreeMem プロセスを呼び出してオブジェクトのメモリ領域を解放します。まさにそのようにして、オブジェクトがシステムから消えます。
生命の誕生が長い妊娠過程であるのと同じように、物体の破壊過程は物体の構築過程よりも単純であるが、死は比較的短命である。これは避けられない法則であるように思われる。
オブジェクトの構築と破棄のプロセス中に、NewInstance と FreeInstance という 2 つの仮想関数が呼び出され、オブジェクト インスタンスのメモリ空間が作成および解放されます。これら 2 つの関数が仮想関数として宣言されている理由は、ユーザーが独自のメモリを管理する必要がある特殊なオブジェクト クラス (一部の特殊な産業用制御プログラムなど) を作成するときに、ユーザーに拡張の余地を与えるためです。
AfterConstruction と BeforeDestruction を仮想関数として宣言することは、将来の派生クラスに、オブジェクト生成後に新しく誕生したオブジェクトに最初の新鮮な空気を吸わせる機会を与え、オブジェクトが消滅する前にオブジェクトが余波を完了できるようにすることでもあります。これはすべて意味のあることです。実際、TForm オブジェクトと TDataModule オブジェクトの OnCreate イベントと OnDestroy イベントは、TForm と TDataModule オーバーロードの 2 つの仮想関数プロセスでそれぞれトリガーされます。
さらに、TObjec には仮想メソッドではない Free メソッドも用意されており、オブジェクトが空 (nil) かどうか不明な場合にオブジェクトを安全に解放するために特別に用意されています。実際、オブジェクトが空かどうかを判断できない場合は、プログラム ロジックが不明確であるという問題があります。ただし、完璧な人間はいないため、間違いを犯す可能性があります。偶発的な間違いを避けるために無料を使用することも良いことです。ただし、正しいプログラムを作成するには、そのような解決策のみに依存する必要はありません。プログラミングの最初の目標は、プログラムの論理的な正しさを保証することです。
興味のある友人は、大量のコードがアセンブリ言語で書かれている System ユニットの元のコードを読むことができます。注意深い人は、TObject のコンストラクター Create とデストラクター Destory がコードを何も書いていないことがわかります。実際、デバッグ状態の [CPU のデバッグ] ウィンドウを通じて、Create と Destory のアセンブリ コードが明確に反映されます。 DELPHI を作成した達人は、ユーザーに複雑なものをあまり提供したくなかったので、ユーザーが単純な概念に基づいてアプリケーションを作成し、ユーザーが実行できる複雑な作業をシステム内に隠すことを望んでいたからです。したがって、System.pas ユニットを公開するときに、ユーザーに TObject がすべてのソースであると思わせるために、これら 2 つの関数のコードが特別に削除され、ユーザー派生クラスは完全に無から始まることになります。これ自体は間違っていません。 DELPHI のこれらの最も重要なコードを読むには、少量のアセンブリ言語の知識が必要ですが、このようなコードを読むことで、DELPHI の世界の起源と発展をより深く理解できます。たとえあまり理解できなくても、少なくともいくつかの基本的なことを理解できれば、DELPHI プログラムを作成する際に非常に役立ちます。
セクション 2 TClass アトム
System.pas ユニットでは、TClass は次のように定義されます。
TClass = TObject のクラス;
これは、TClass が TObject のクラスであることを意味します。 TObject 自体がクラスであるため、TClass はいわゆるクラスのクラスです。
概念的には、TClass はクラスの一種、つまりクラスです。ただし、DELPHI のクラスが VMT データの一部を表すことはわかっています。したがって、このクラスは VMT データ項目に対して定義された型と考えることができ、実際には VMT データを指すポインター型です。
従来の C++ 言語では、クラスの型を定義できませんでした。オブジェクトがコンパイルされると、オブジェクトは修正され、クラスの構造情報は絶対マシンコードに変換され、完全なクラス情報はメモリ内に存在しなくなります。一部の高レベルのオブジェクト指向言語は、クラス情報の動的なアクセスと呼び出しをサポートできますが、多くの場合、複雑な内部解釈メカニズムとより多くのシステム リソースが必要になります。 DELPHI の Object Pascal 言語は、高レベルのオブジェクト指向言語の優れた機能の一部を吸収しながら、プログラムを直接マシンコードにコンパイルするという従来の利点を維持しており、高度な機能とプログラムの効率の問題を完全に解決します。
DELPHI がアプリケーション内に完全なクラス情報を保持しているからこそ、実行時にクラスを変換および識別するなどの高度なオブジェクト指向機能を提供できます。この機能では、クラスの VMT データが重要な中心的な役割を果たします。興味のある方は、System ユニットの AsClass と IsClass の 2 つのアセンブリ プロセスを読んで、クラスと VMT データについての理解を深めてください。
デルフィのアトミックワールド (2)
キーワード: Delphi コントロールのその他
セクション 2 TClass アトム
System.pas ユニットでは、TClass は次のように定義されます。
TClass = TObject のクラス;
これは、TClass が TObject のクラスであることを意味します。 TObject 自体がクラスであるため、TClass はいわゆるクラスのクラスです。
概念的には、TClass はクラスの一種、つまりクラスです。ただし、DELPHI のクラスが VMT データの一部を表すことはわかっています。したがって、このクラスは VMT データ項目に対して定義された型と考えることができ、実際には VMT データを指すポインター型です。
従来の C++ 言語では、クラスの型を定義できませんでした。オブジェクトがコンパイルされると、オブジェクトは修正され、クラスの構造情報は絶対マシンコードに変換され、完全なクラス情報はメモリ内に存在しなくなります。一部の高レベルのオブジェクト指向言語は、クラス情報の動的なアクセスと呼び出しをサポートできますが、多くの場合、複雑な内部解釈メカニズムとより多くのシステム リソースが必要になります。 DELPHI の Object Pascal 言語は、高レベルのオブジェクト指向言語の優れた機能の一部を吸収しながら、プログラムを直接マシンコードにコンパイルするという従来の利点を維持しており、高度な機能とプログラムの効率の問題を完全に解決します。
DELPHI がアプリケーション内に完全なクラス情報を保持しているからこそ、実行時にクラスを変換および識別するなどの高度なオブジェクト指向機能を提供できます。この機能では、クラスの VMT データが重要な中心的な役割を果たします。興味のある方は、System ユニットの AsClass と IsClass の 2 つのアセンブリ プロセスを読んで、クラスと VMT データについての理解を深めてください。
クラスの型を使用すると、クラスを変数として使用できます。クラス変数は特別なオブジェクトとして理解でき、オブジェクトと同じようにクラス変数のメソッドにアクセスできます。例: 次のプログラムの一部を見てみましょう。
タイプ
TSampleClass = TSampleObject のクラス。
TSampleObject = クラス( TObject )
公共
コンストラクター作成;
デストラクタ オーバーライド;
クラス関数 GetSampleObjectCount:Integer;
プロシージャ GetObjectIndex:Integer;
終わり;
変数
aSampleClass : TSampleClass;
aクラス : Tクラス;
このコードでは、クラス TSampleObject とその関連クラス型 TSampleClass、および 2 つのクラス変数 aSampleClass と aClass を定義します。さらに、TSampleObject クラスのコンストラクター、デストラクター、クラス メソッド GetSampleObjectCount、およびオブジェクト メソッド GetObjectIndex も定義しました。
まず、クラス変数 aSampleClass と aClass の意味を理解しましょう。
明らかに、123 個の定数値を整数変数 i に割り当てるのと同じように、TSampleObject と TObject を定数値として扱い、それらを aClass 変数に割り当てることができます。したがって、クラス型、クラス、およびクラス変数間の関係は、型、定数、および変数間の関係ですが、オブジェクト レベルではなくクラス レベルでの関係になります。もちろん、TObject を aSampleClass に直接割り当てることは合法ではありません。aSampleClass は TObject 派生クラス TSampleObject のクラス変数であり、TObject には TSampleClass 型と互換性のあるすべての定義が含まれていないからです。逆に、TSampleObject を aClass 変数に割り当てることは合法です。TSampleObject は TObject の派生クラスであり、TClass 型と互換性があるためです。これは、オブジェクト変数の代入と型一致関係とまったく同じです。
次に、クラスメソッドとは何かを見てみましょう。
いわゆるクラスメソッドとは、上で定義した GetSampleObjectCount メソッドなど、クラスレベルで呼び出されるメソッドを指し、予約語クラスで宣言されたメソッドです。クラス メソッドは、オブジェクト レベルで呼び出されるオブジェクト メソッドとは異なります。オブジェクト メソッドはすでに私たちに馴染みがあり、クラス メソッドは常に、すべてのクラス オブジェクトの共通特性にアクセスして制御し、オブジェクトを集中管理するレベルで使用されます。 TObject の定義には、ClassName、ClassInfo、NewInstance などの多数のクラス メソッドが含まれています。このうち NewInstance も virtual、つまり仮想クラスメソッドとして定義されています。これは、派生サブクラスの NewInstance の実装メソッドを書き換えて、そのクラスのオブジェクト インスタンスを特別な方法で構築できることを意味します。
クラス メソッドで識別子 self を使用することもできますが、その意味はオブジェクト メソッドの self とは異なります。クラス メソッドの self はそれ自体のクラス、つまり VMT へのポインタを表し、オブジェクト メソッドの self はオブジェクト自体、つまりオブジェクト データ空間へのポインタを表します。クラス メソッドはクラス レベルでのみ使用できますが、オブジェクトを通じてクラス メソッドを呼び出すことはできます。たとえば、オブジェクト TObject のクラス メソッド ClassName は、ステートメント aObject.ClassName を通じて呼び出すことができます。これは、オブジェクト ポインタが指すオブジェクト データ空間の最初の 4 バイトがクラス VMT へのポインタであるためです。逆に、クラス レベルでオブジェクト メソッドを呼び出すことはできず、TObject.Free のようなステートメントは不正でなければなりません。
コンストラクターはクラス メソッドであり、デストラクターはオブジェクト メソッドであることに注意してください。
何?コンストラクターはクラス メソッド、デストラクターはオブジェクト メソッドです。何か間違いはありましたか?
オブジェクトを作成するときは、明らかに次のようなステートメントを使用します。
aObject := TObject.Create;
これは明らかに TObject クラスの Create メソッドを呼び出しています。オブジェクトを削除する場合は、次のステートメントを使用します。
aObject.Destroy;
Free メソッドを使用してオブジェクトを解放した場合でも、オブジェクトの Destroy メソッドが間接的に呼び出されます。
理由は非常に簡単です。オブジェクトが構築される前は、オブジェクトはまだ存在せず、クラス メソッドを使用してオブジェクトを作成することしかできません。逆に、オブジェクトを削除すると、クラスではなく、既存のオブジェクトが削除されます。
最後に、架空のコンストラクターの問題について説明します。
従来の C++ 言語では、仮想デストラクターを実装できますが、仮想コンストラクターの実装は困難な問題です。従来の C++ 言語にはクラス型が存在しないためです。グローバル オブジェクトのインスタンスはコンパイル時にグローバル データ空間に存在し、関数のローカル オブジェクトもコンパイル時にスタック空間にマップされるインスタンスです。インスタンスは new 演算子を使用して固定クラス構造に配置されます。コンストラクターは、生成されたオブジェクト インスタンスを初期化する単なるオブジェクト メソッドです。従来の C++ 言語には実際のクラス メソッドはなく、いわゆる静的クラスベースのメソッドを定義できたとしても、それらは最終的には特別なグローバル関数として実装され、仮想クラス メソッドは特定のオブジェクトのみを対象とすることができます。効率的なインスタンス。したがって、従来の C++ 言語では、特定のオブジェクト インスタンスが生成される前に、生成されるオブジェクトに基づいてオブジェクト自体を構築することは不可能であると考えられています。それは実際には不可能です。なぜなら、これは論理に自己矛盾のパラドックスを生み出すことになるからです。
ただし、動的なクラス型情報、真の仮想クラス メソッド、および DELPHI のクラスに基づいて実装されたコンストラクターという重要な概念があるからこそ、仮想コンストラクターを実装できます。オブジェクトはクラスによって生成され、オブジェクトは成長する赤ちゃんのようなものであり、クラスはその母親です。赤ちゃん自身は将来どのような人間になるかわかりませんが、母親は独自の教育方法でさまざまな子供を育てます。人々、原則は同じです。
TComponent クラスの定義では、コンストラクター Create が仮想として定義されているため、さまざまな種類のコントロールが独自の構築メソッドを実装できます。これがTClassが作るクラスという概念の素晴らしさであり、DELPHIの素晴らしさでもあります。
................................................................... ..
第 3 章 WIN32 における時間と空間の見方
年老いた父は、地面でおもちゃで遊んでいる小さな孫を見て、私にこう言いました。「この子は、あなたが子供の頃のあなたと同じです。彼は物を分解するのが好きで、最後まで見た後に初めて立ち止まります。 」子供の頃を思い出してみると、おもちゃの車や小さな目覚まし時計、オルゴールなどをよく分解して、よく母に叱られていました。
私がコンピューターの基本原理を初めて理解したのは、分解したオルゴールに関係していました。私が高校生の頃の漫画で、白いひげを生やしたおじさんがスマートマシンの理論を説明していて、口ひげを生やしたおじさんがコンピューターとオルゴールについて話していました。コンピュータの中央処理装置はオルゴール内の発音に使用される音符の列であり、コンピュータプログラムはオルゴール内の小さな円筒に密集した突起であり、その小さな円筒の回転に相当します。中央処理装置の回転に合わせて指示針が自然に動き、小さな円筒上の音楽を表す凹凸が譜面の振動を制御し、中央処理装置によるプログラムの実行と同等の指示を生み出します。オルゴールは、職人が小さな筒に刻んだ楽譜に従って美しいメロディーを奏で、プログラマーがあらかじめプログラムしたプログラムに基づいてコンピューターが複雑な処理を完成させます。大学に進学した後、白いひげを生やした老人が科学界の巨人チューリングであり、彼の有限オートマトン理論が情報革命全体の発展を促進し、口ひげを生やした老人がコンピュータの父であるフォン・ノイマンであることを知りました。 . コンピュータ アーキテクチャは依然としてコンピュータの主要なアーキテクチャ構造です。オルゴールは無駄に解体されませんでした、お母さんは安心してください。
シンプルかつ深い理解があってこそ、奥深く簡潔な作品を生み出すことができます。
この章では、Windows 32 ビット オペレーティング システムでのプログラミングに関連する基本概念について説明し、WIN32 における時間と空間の正しい見方を確立します。この章を読んだ後、プログラム、プロセス、スレッドをより深く理解し、実行可能ファイル、ダイナミック リンク ライブラリ、およびランタイム パッケージの原理を理解し、メモリ内のグローバル データ、ローカル データ、およびパラメータに関する真実を明確に理解できるようになることを願っています。 。
セクション 1 プロセスの理解
歴史的な理由により、Windows は DOS から生まれました。 DOS の時代には、常にプログラムという概念しかなく、プロセスという概念はありませんでした。当時、プロセスという概念を持っていたのは UNIX や VMS などの通常のオペレーティング システムだけであり、マルチプロセスとはミニコンピュータ、端末、複数のユーザーを意味し、またお金も意味していました。ほとんどの場合、私は比較的安価なマイコンと DOS システムしか使用できませんでしたが、プロセスやミニコンピュータに触れるようになったのは、オペレーティング システムを勉強していたときだけでした。
それは Windows 3 以降でした。従来、DOSでは同時に1つのプログラムしか実行できませんでしたが、Windowsでは複数のプログラムを同時に実行できるようになりました。 DOS でプログラムを実行している間、同じプログラムを同時に実行することはできませんが、Windows では、同じプログラムの 2 つ以上のコピーを同時に実行することができ、実行中のプログラムの各コピーがプロセスになります。より正確に言うと、プログラムを実行するたびにタスクが生成され、各タスクがプロセスになります。
プログラムとプロセスを一緒に理解すると、プログラムという言葉は静的なものを指すと考えることができます。典型的なプログラムは、EXE ファイルまたは EXE ファイルと複数の DLL ファイルで構成される静的なコードとデータです。プロセスとは、メモリ内で動的に実行されるコードと動的に変化するデータであるプログラムの実行です。静的プログラムを実行する必要がある場合、オペレーティング システムはこの操作のために特定のメモリ空間を提供し、静的プログラム コードとデータをこれらのメモリ空間に転送し、この空間内でプログラム コードとデータを再配置してマップします。内部で実行されるため、動的なプロセスが作成されます。
同じプログラムの 2 つのコピーが同時に実行されるということは、システム メモリ内に 2 つのプロセス空間が存在することを意味しますが、それらのプログラム機能は同じですが、動的に変化する異なる状態になります。
プロセスの実行時間の観点から、各プロセスが同時に実行されることを専門用語で並列実行または同時実行と呼びます。しかし、これは主にオペレーティング システムが私たちに与える表面的な感覚であり、実際には、各プロセスはタイムシェアリング方式で実行されます。つまり、各プロセスは、プロセスのプログラム命令を実行するために CPU 時間を交代で占有します。 CPU の場合、同時に実行されるのは 1 つのプロセスの命令だけです。オペレーティング システムは、スケジュールされたプロセスの動作の背後にあるマニピュレーターであり、CPU で実行される各プロセスの現在のステータスを常に保存し、切り替えるため、スケジュールされた各プロセスは完全かつ継続的に実行されていると認識されます。プロセスのタイムシェアリング スケジューリングは非常に高速であるため、すべてのプロセスが同時に実行されているような印象を与えます。実際、真の同時操作はマルチ CPU ハードウェア環境でのみ可能です。後でスレッドについて説明するときに、スレッドが実際にプロセスを駆動するものであり、さらに重要なことに、スレッドがプロセス領域を提供することがわかります。
プロセスが占める空間という点では、各プロセス空間は比較的独立しており、各プロセスは独自の独立した空間で実行されます。プログラムにはコード空間とデータ空間の両方が含まれ、コードとデータの両方がプロセス空間を占有します。 Windows は、各プロセスが必要とするデータ領域に実際のメモリを割り当て、一般にコード領域の共有方法を使用して、プログラムの 1 つのコードをプログラムの複数のプロセスにマッピングします。これは、プログラムに 100K のコードがあり、100K のデータ スペースが必要な場合、つまり合計 200K のプロセス スペースが必要な場合、オペレーティング システムはプログラムの初回実行時に 200K のプロセス スペースを割り当て、さらに 200K のプロセス スペースを割り当てます。プログラムが 2 回目に実行されるとき、オペレーティング システムは 100K のデータ スペースのみを割り当てますが、コード スペースは前のプロセスのスペースを共有します。
上記は、Windows オペレーティング システムにおけるプロセスの基本的な時間と空間のビューです。実際、Windows の 16 ビット オペレーティング システムと 32 ビット オペレーティング システムでは、プロセスの時間と空間のビューに大きな違いがあります。
時間の観点から見ると、Windows 3.x などの 16 ビット Windows オペレーティング システムのプロセス管理は、実際には単なるマルチタスク管理オペレーティング システムです。さらに、オペレーティング システムのタスク スケジューリングは受動的です。タスクがメッセージの処理を放棄しない場合、オペレーティング システムは待機する必要があります。 16 ビット Windows システムのプロセス管理の欠陥により、プロセスが実行されると、そのプロセスが CPU リソースを完全に占有します。当時、Microsoft は、16 ビット Windows が他のタスクをスケジュールできるようにするために、Windows アプリケーションの開発者が心の広いプログラマーであることを賞賛し、彼らは喜んでさらに数行のコードを書いて、オペレーティング·システム。それどころか、Windows 95 や NT などの WIN32 オペレーティング システムには、実際のマルチプロセスおよびマルチタスク オペレーティング システム機能があります。 WIN32 のプロセスは、オペレーティング システムによって完全にスケジュールされます。実行中のプロセスのタイム スライスが終了すると、プロセスがまだデータを処理しているかどうかに関係なく、オペレーティング システムは次のプロセスにアクティブに切り替えます。厳密に言えば、16ビットのWindowsオペレーティングシステムは完全なオペレーティングシステムと見なすことはできませんが、32ビットWin32オペレーティングシステムは真のオペレーティングシステムです。もちろん、MicrosoftはWin32が16ビットウィンドウの欠点を補うとは言いませんが、Win32が商業的方法である「Preemptive Multitasking」と呼ばれる高度なテクノロジーを実装すると主張しています。
スペースの観点からは、16ビットWindowsオペレーティングシステムのプロセススペースは比較的独立していますが、プロセスは互いのデータ空間に簡単にアクセスできます。これらのプロセスは実際には同じ物理空間内の異なるデータセグメントであり、不適切なアドレス操作は誤ったスペースの読み取りと書き込みを簡単に引き起こし、オペレーティングシステムをクラッシュさせる可能性があるためです。ただし、Win32オペレーティングシステムでは、各プロセススペースは完全に独立しています。 Win32は、各プロセスに最大4Gの仮想および連続アドレススペースを提供します。いわゆる連続アドレススペースは、各プロセスに16ビットウィンドウのセグメント化されたスペースではなく、$ 00000000から$ FFFFFFFFのアドレススペースがあることを意味します。 Win32では、他のプロセススペースのデータに意図せずに読み取りおよび書き込み操作を心配する必要はありません。また、他のプロセスがあなたの仕事に嫌がらせをすることを心配する必要はありません。同時に、Win32がプロセスに提供する連続4G仮想スペースは、ハードウェアをサポートするオペレーティングシステムによってマッピングされますが、システムはバイトを無駄にしません。 。
セクション2プロセススペース
Delphiを使用してWin32アプリケーションを作成する場合、実行中のプロセスの内部の世界についてはめったに気にしません。 Win32は、プロセスに4Gの連続仮想プロセススペースを提供しているため、おそらく世界最大のアプリケーションは現在その一部しか使用していません。プロセススペースは無制限であるように見えますが、4Gプロセススペースは仮想であり、マシンの実際のメモリはこれとはほど遠い場合があります。このプロセスにはこのような広大なスペースがありますが、一部の複雑なアルゴリズムプログラムは、スタックオーバーフロー、特に多数の再帰アルゴリズムを含むプログラムのために実行できません。
したがって、4Gプロセス空間の構造、物理的記憶との関係などを深く理解することは、実際の開発作業で正しい方法を使用できるように、Win32の時空の世界をより明確に理解するのに役立ちます。さまざまな困難な問題を解決するための世界観と方法論。
次に、単純な実験を使用して、Win32のプロセススペースの内部世界を理解します。これには、カップレジスタとアセンブリ言語に関する知識が必要になる場合がありますが、簡単な言語で説明しようとしました。
Delphiが開始されると、Project1プロジェクトが自動的に生成され、開始されます。たとえば、Project1.dprの元のプログラムのどこにでもブレークポイントを設定します。たとえば、開始文にブレークポイントを設定します。次に、プログラムを実行すると、ブレークポイントに到達すると自動的に停止します。現時点では、プロセス空間の内部構造を観察するために、デバッグツールにCPUウィンドウを開くことができます。
現在の命令ポインターレジスタEIPは、プログラム命令が配置されているアドレスの2つの16進数桁から$ 0043E4B8で停止します。 $ ffffffffの$ 00000000を占有するために$ 00000000を占有するプロセススペース。
CPUウィンドウのコマンドボックスで、プロセススペースの内容を調べることができます。 $ 00400000未満のスペースのコンテンツを表示すると、$ 00400000未満のコンテンツに表示される一連の疑問符があります。現時点でグローバル変数ヒンスタンスの16進価値を見ると、$ 00400000でもあることがわかります。 Hinstanceはプロセスインスタンスのハンドルを反映していますが、実際、プログラムがメモリにロードされたときの16ビットウィンドウでも、それは開始アドレス値です。したがって、プロセスのプログラムは$ 00400000からロードされていると考えることができます。つまり、4G仮想スペースの4mから始まるスペースは、プログラムがロードされるスペースです。
$ 00400000以降および$ 0044d000の前から、それは主にプログラムコードとグローバルデータのアドレス空間です。 CPUウィンドウのスタックボックスで、現在のスタックのアドレスを表示できます。同様に、現在のスタックアドレススペースは、$ 0067b000から$ 00680000の長さで、$ 5000であることがわかります。実際、プロセスの最小スタックスペースサイズは5000ドルです。これは、Delphiプログラムをコンパイルする際にProjectoptionsのリンカーページに設定されたMINスタックサイズ値に基づいて取得され、1000ドルです。スタックは、ハイエンドアドレスから、実行中のスタックで十分ではありませんプロセススペース。 Delphiプログラムをコンパイルするとき、Projectoptionsのリンカーページの最大スタックサイズの値を設定することで、増加できる最大スタックスペースを制御できます。特に、深いサブルーチンの呼び出し関係を含むプログラムまたは再帰的アルゴリズムを使用するプログラムでは、最大スタックサイズの値を合理的に設定する必要があります。サブルーチンを呼び出すにはスタックスペースが必要であり、スタックが使い果たされた後、システムは「スタックオーバーフロー」エラーをスローします。
スタックスペースの後のプロセススペースは自由スペースである必要があるようです。実際、これはそうではありません。Win32の関連情報は、80,000,000ドルの後の2Gスペースがシステムで使用されるスペースであると述べています。このプロセスは、実際に2gのスペースしか所有していないようです。実際、プロセスが実際に所有できるスペースは2Gでさえありません。なぜなら、$ 00000000から$ 00400000の4Mスペースも制限されたエリアです。
しかし、何があっても、私たちのプロセスが使用できるアドレスはまだ非常に広いです。特に、スタックスペースの後、80,000,000ドルの間、それはプロセススペースの主要な戦場です。システムからプロセスによって割り当てられたメモリスペースはこのスペースにマッピングされ、プロセスによってロードされた動的リンクライブラリはこのスペースにマッピングされます。新しいスレッドのスレッドスタックスペースもこのスペースにマッピングされます。メモリの割り当てを含む操作はすべて、このスペースにマッピングされます。ここで説明したマッピングは、デバッグ中のCPUウィンドウのコマンドボックスの「」の文字列と同じように、実際のメモリとこの仮想空間との対応を意味します。 ????」。