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?C 仮想メソッド テーブル) を指します。次の空間は、オブジェクト自身のメンバデータを格納する空間であり、オブジェクトの最も原始的な祖先クラスのデータメンバからオブジェクトクラスのデータメンバまでの全体の順序と、そのオブジェクトのデータメンバの順序で格納される。データ メンバーはクラスの各レベルで定義されます。
クラスの仮想メソッド テーブル (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 データについての理解を深めてください。
...
以下の内容には、架空のコンストラクタの理解、Interface の実装機構、例外処理の実装機構なども含まれます。 DLPHI の基本原理。メーデー後に完成させて皆さんに提供できればと思っています。