セクション 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 ビット Windows の欠点を補っているとは言いませんが、WIN32 は商用手法である「プリエンプティブ マルチタスク」と呼ばれる高度なテクノロジを実装していると主張しています。
スペースの観点から見ると、16 ビット Windows オペレーティング システムのプロセス スペースは比較的独立していますが、プロセスはお互いのデータ スペースに簡単にアクセスできます。これらのプロセスは実際には同じ物理空間内の異なるデータ セグメントであり、不適切なアドレス操作により誤った空間の読み取りと書き込みが容易に発生し、オペレーティング システムがクラッシュする可能性があるためです。ただし、WIN32 オペレーティング システムでは、各プロセス空間は完全に独立しています。 WIN32 は、各プロセスに最大 4G の仮想連続アドレス空間を提供します。いわゆる連続アドレス空間とは、16 ビット Windows のセグメント化された空間ではなく、各プロセスが $00000000 から $FFFFFFFF までのアドレス空間を持つことを意味します。 WIN32 では、読み取りおよび書き込み操作が他のプロセス スペースのデータに意図せず影響を与えることを心配する必要はありません。また、他のプロセスが作業を妨害することを心配する必要もありません。同時に、WIN32 によってプロセスに提供される連続 4G 仮想空間は、ハードウェアのサポートを受けてオペレーティング システムによってマップされた物理メモリです。このような広大な仮想空間があるにもかかわらず、システムは 1 バイトも無駄にすることはありません。 .物理メモリ。
セクション 2 プロセス空間
DELPHI を使用して WIN32 アプリケーションを作成する場合、実行中のプロセスの内部世界についてはほとんど気にしません。 WIN32 はプロセスに 4G の連続仮想プロセス スペースを提供するため、おそらく世界最大のアプリケーションは現在その一部しか使用していません。プロセス領域は無制限であるように見えますが、4G プロセス領域は仮想的なものであり、マシンの実際のメモリはこれとはかけ離れている可能性があります。プロセスには非常に広大なスペースがありますが、一部の複雑なアルゴリズム プログラム、特に多数の再帰アルゴリズムを含むプログラムはスタック オーバーフローにより実行できなくなります。
したがって、4G プロセス空間の構造や物理メモリとの関係などを深く理解することは、WIN32 の時空世界をより明確に理解するのに役立ち、実際の開発作業で正しい方法を使用できるようになります。 . 様々な難題を解決するための世界観と方法論。
次に、簡単な実験を使用して、WIN32 のプロセス空間の内部世界を理解します。 CUPのレジスタやアセンブリ言語の知識が必要になるかもしれませんが、簡単な言葉で説明してみました。
DELPHI を起動すると Project1 プロジェクトが自動生成されるので、それから開始します。 Project1.dpr の元のプログラム内の任意の場所にブレークポイントを設定します。たとえば、開始文にブレークポイントを設定します。次にプログラムを実行すると、ブレークポイントに到達すると自動的に停止します。この時点で、デバッグ ツールで CPU ウィンドウを開いて、プロセス空間の内部構造を観察できます。
現在の命令ポインタレジスタ Eip は $0043E4B8 で停止しています。プログラム命令が配置されているアドレスの 16 進数の上位 2 桁が両方とも 0 であることから、現在のプログラムは 4G の最下位のアドレス位置にあることがわかります。プロセス空間は、$00000000 から $FFFFFFFF のかなり小さなアドレス空間を占有します。
CPU ウィンドウのコマンド ボックスで、プロセス空間の内容を調べることができます。 $00400000 未満の領域のコンテンツを表示すると、$00400000 未満のコンテンツに一連の疑問符「????」が表示されます。これは、アドレス空間が実際の物理空間にマッピングされていないためです。このときグローバル変数HInstanceの16進数値を見てみると、やはり$00400000となっていることがわかります。 HInstance はプロセス インスタンスのハンドルを反映しますが、実際には、16 ビット Windows においても、プログラムがメモリにロードされるときの開始アドレス値です。したがって、そのプロセスのプログラムは$00400000からロードされている、つまり4G仮想空間の4Mから始まる空間がプログラムがロードされている空間であると考えることができます。
$00400000以降、$0044D000以前は主にプログラムコードとグローバルデータのアドレス空間です。 CPU ウィンドウのスタック ボックスで、現在のスタックのアドレスを表示できます。同様に、現在のスタック アドレス空間は $0067B000 ~ $00680000 で、長さは $5000 であることがわかります。実際、プロセスの最小スタック スペース サイズは $5000 です。これは、DELPHI プログラムのコンパイル時に ProjectOptions の [リンカー] ページで設定された最小スタック サイズ値に基づいて取得され、$1000 を加えたものです。スタックは上位アドレスから下位アドレスに向かって増加します。プログラムの実行時にスタックが不十分な場合、システムはスタック領域のサイズを下位アドレスに向かって自動的に増加させます。プロセススペース。 DELPHI プログラムをコンパイルするときに、ProjectOptions の [Linker] ページで [Max stack size] の値を設定することで、増加できる最大スタック領域を制御できます。特に、深いサブルーチン呼び出し関係が含まれるプログラムや再帰的アルゴリズムを使用するプログラムでは、最大スタック サイズの値を合理的に設定する必要があります。サブルーチンの呼び出しにはスタック領域が必要であり、スタックが使い果たされると、システムは「スタック オーバーフロー」エラーをスローするためです。
スタック領域以降のプロセス領域は空き領域にする必要があるようです。実際には、そうではありません。WIN32 の関連情報には、80,000,000 ドル以降の 2G スペースがシステムによって使用されるスペースであると記載されています。プロセスが実際に所有できるスペースは 2G のみのようです。実際、$00000000 から $00400000 までの 4M 領域も制限領域であるため、プロセスが実際に所有できる領域は 2G にも満たありません。
しかし、いずれにせよ、私たちのプロセスが使用できるアドレスは依然として非常に広範囲です。特にスタック領域以降と 80,000,000 ドルの間は、プロセス領域の主戦場です。プロセスによってシステムから割り当てられたメモリ空間はこの空間にマッピングされ、プロセスによってロードされたダイナミック リンク ライブラリはこの空間にマッピングされ、新しいスレッドのスレッド スタック スペースもこの空間にマッピングされます。メモリ割り当てを伴う操作はすべてこのスペースにマップされます。ここで言うマッピングとは、実メモリとこの仮想空間との対応付けを意味しており、デバッグ時のCPUウィンドウのコマンドボックスの「」の文字列と同様に、実メモリにマッピングされていないプロセス空間は使用できないことに注意してください。 ???」。
…………
読んでいただきありがとうございます!