デルファイの構造と破壊の分析
Delphiの1つのオブジェクトモデル:2
1.1オブジェクト名はどういう意味ですか? 2
1.2オブジェクトはどこに保存されていますか? 2
1.3オブジェクトに何が保存されていますか?それらはどのように保存されますか?
2コンストラクターとオブジェクトの作成5
2.1コンストラクターとは何ですか? (「特別」クラス方法)5
2.2オブジェクトを作成するプロセス全体5
2.3コンストラクターの代替使用(コンストラクターの多型を実装するためのクラス参照を使用)6
3デストラクタと破壊オブジェクト7
3.1デストラクタとは(「自然」仮想方法)7
3.2オブジェクト破壊のプロセス全体7
3.3破壊、無料、Freeandnil、リリースの使用と違い7
4 VCL構造と破壊アーキテクチャ8
5コンストラクターとデストラクタを正しく使用します9
デルファイの構造と破壊の分析
要約:VCL/RTLの研究を通じて、このペーパーでは、コンストラクターと破壊者の実装メカニズム、およびVCLのオブジェクトのアーキテクチャを分析し、オブジェクトを正しく作成および解放する方法を説明します。
キーワード:構築、破壊、オブジェクトの作成、オブジェクト、ヒープ、スタック、多型を破壊します。
著者:Majorsoft
質問
Delphiのコンストラクターとデストラクタの実装メカニズムは何ですか?オブジェクトを正しく作成およびリリースする方法は?
解決
建設と破壊の使用方法は、Delphiを使用するプロセスで遭遇します。以下は、VCL/RTLソースコードの研究を通じて、コンストラクターと破壊者の実装メカニズムを理解するためです。
Delphiの1つのオブジェクトモデル:
1.1オブジェクト名はどういう意味ですか?
C ++とは異なり、Delphiのオブジェクト名(変数とも呼ばれます)はオブジェクトの参照を表し、オブジェクト自体を表しません。オブジェクト自体は、「オブジェクト参照モデル」と呼ばれるオブジェクトへのポインターに相当します。図に示されているように:
obj(オブジェクト名)実際のオブジェクト
VMTエントリアドレス
データメンバー
図1オブジェクト名はメモリ内のオブジェクトを指します
1.2オブジェクトはどこに保存されていますか?
各アプリケーションは、割り当てられたメモリを分割して4つの領域にぶつかります。
コード領域
グローバルデータ領域
ヒープエリア
スタックエリア
図2プログラムメモリスペース
コード領域:プログラムコードをプログラムに保存します。
グローバルデータ領域:グローバルデータを保存します。
ヒープ領域:動的データ(Delphiのオブジェクトと文字列を含む)を保存する「フリーストレージエリア」とも呼ばれます。スコープは、デストラクタが呼び出されるまで、アプリケーションのライフサイクル全体です。
スタック領域:「自動ストレージエリア」とも呼ばれ、C ++でローカルデータを保存します。スコープは関数内にあり、システムが呼び出された後、システムはすぐにスタックスペースをリサイクルします。
C ++では、ヒープ、またはグローバルデータにオブジェクトを作成できます。 Delphiでは、すべてのオブジェクトがヒープストレージエリアの上に構築されているため、Delphiコンストラクターは自動的に呼び出すことはできませんが、プログラマ自身が呼び出す必要があります(デザイナーのコンポーネントをドラッグし、オブジェクトはDelphiによって作成されます)。次のプログラムでは、DelphiとC ++でオブジェクトの作成の違いを説明しています。
デルファイ:
手順CreateObject(var fooobjref:tfooobject);
始める
fooobjref:= tfooobject.create;
//プログラマーから呼び出された後、オブジェクトは存在します。
fooobject.caption = 'createObject()'のスタックで作成されています。
終わり;
そしてC ++で:
tfooobject CreateObject(void);
{
tfoobject fooobject; //ローカルオブジェクトを作成します
// static tfooobject fooobject; //静的ローカルオブジェクトを作成します
//オブジェクトは、デフォルトのコンストラクターを呼び出すことによって自動的に作成され、オブジェクトはこの時点で関数スタックに作成されます。
fooobject.caption = 'createObject()'のスタックで作成されています。
fooobjectを返します。
//オブジェクトが返されるときにコピーされ、関数呼び出しが終了した後に元の作成されたオブジェクトが自動的に破壊されます}
tfooobject fooobject2; //グローバルオブジェクトを作成します。
void main();
{tfooobject* pfooobject = new tfooobject;
//ヒープオブジェクトを作成します。関数が呼び出された後、オブジェクトはまだ存在し、コピーする必要はありません。 }
1.3オブジェクトに何が保存されていますか?彼らはどのように保管されていますか?
C ++とは異なり、Delphiのオブジェクトはデータメンバーと仮想メソッドテーブル(VMTS)のエントリアドレスのみを保存しますが、図に示すようにメソッドを保存しません。
オブジェクト仮想メソッドテーブルコードセグメント
VMTアドレス
名前:文字列
幅:整数;
CH1:char;
…
proc1
FUNC1
…
procn
funcn
…
図3オブジェクトの構造...
上記の声明についていくつか質問があるかもしれません。次の手順を参照してください。
tsizealigntest = class
プライベート
I:整数;
CH1、CH2:CHAR;
J:整数;
公共
手順showmsg;
手順virtmtd;
終わり;
memo1.lines.add(inttostr(sizetest.instancesize)+':instancesize');
memo1.lines.add(inttostr(integer(sizetest))+'< - start addr');
memo1.lines.add(inttostr(integer(@(sizetest.i)))+'<-sizetest.i');
memo1.lines.add(inttostr(integer(@(sizetest.ch1)))+'<-sizetest.ch1');
memo1.lines.add(inttostr(integer(@(sizetest.ch2)))+'<-sizetest.ch2');
memo1.lines.add(inttostr(integer(@(sizetest.j)))+'<-sizetest.j');
結果は次のとおりです。
16:インスタンスサイズ
14630724 <-start addr
14630728 <-sizetest.i
14630732 <-sizetest.ch1
14630733 <-sizetest.ch2
14630736 <-sizetest.j
データメンバーとVMTエントリアドレスは、16バイトを占めています。
では、メンバー関数はどこに保存されますか? DelphiはRTL(ランタイムタイプライブラリ)に基づいているため、すべてのメンバー関数はクラスに保存されます。これはメソッドポインターです。これは、メンバー関数のエントリアドレスを指します。では、メンバー関数のエントリアドレスを見つける方法は?静的関数の場合、この作業はコンパイラによって行われます。メンバー関数のエントリアドレスは、クラスオブジェクトリファレンス/ポインターのタイプに従って直接見つかります(オブジェクトはで存在する必要はありません。今回は、いわゆる静的バインディング(動的な方法を含む)、ランタイムテーブルVMTエントリアドレスの仮想メソッドテーブル(つまり、オブジェクトの最初の4バイト)それ以外の場合は、この時点で存在する必要があります。メンバー関数のエントリアドレスを見つけるために、ポインターアクセスエラーが発生します。
知らせ
上記のように、すべてのメンバー関数はクラスに保存され、実際に仮想メソッドテーブルVMTが含まれています。 Delphiのコードオートコンプリート関数(コンパイル情報に依存します)から、オブジェクト名を入力してから「」を入力した後、Delphiが再コンパイルし、すべてのデータメンバーとすべての静的メソッド、すべての仮想メソッド、すべてのクラスをリストすることがわかります。メソッド、すべてのコンストラクターおよびデストラクタでは、これが当てはまる場合は試してみることができます。
クラス仮想メソッドテーブルのVMTエントリアドレス
データメンバーテンプレート情報
静的メソッドテーブルなど
仮想メソッドテーブルVMT
物体
VMTエントリアドレス
データメンバー
上記のプログラムは、下の図に示すように、4バイト(Windowsデフォルトアライメント)で整列するオブジェクトデータメンバー(物理データ構造)のアライメントも示しています。
VMT入り口Addr
私
CH1CH2
j
2コンストラクターとオブジェクトの作成
2.1コンストラクターとは何ですか? (「特別」方法)
OO(オブジェクト指向)のアイデアのセマンティクスから、コンストラクターはオブジェクトの作成に責任がありますが、OOP言語の実装に関しては、DelphiまたはC ++であろうと、コンストラクターはオブジェクトの初期化のみを行います(内部サブオブジェクトを呼び出す)。
さらに、C ++とは異なり、Delphiはコンストラクターの別のメソッドタイプ(mkconstructor、line/source/rtl/common/typinfo.pas、delphiインストールディレクトリの行125を参照)を定義します。 。クラス(クラス名/クラス参照/クラスのポインター)を通じてのみ呼び出すことができますが、一般的なクラスのメソッドはクラスとオブジェクトの両方を介して呼び出すことができます。 、そしてクラスの方法では、自己をクラスを指しています。クラスを指しています。通常、データメンバーを初期化して実際のオブジェクトにします。これは、自己パラメーターのおかげです。
デフォルトでは、コンストラクターはそれを仮想的な方法として設定し、派生したクラスでオーバーライドできます(2.4を参照)、またはそれを行うことができます。複数のコンストラクターを作成し、派生クラスの親クラスのコンストラクターを直接オーバーレイすることもできます。構造と破壊の(4を参照)
2.2オブジェクト作成のプロセス全体
オブジェクトを作成する完全なプロセスには、スペースの割り当て、物理データ構造の構築、初期化、内部サブオブジェクトの作成が含まれます。上記のように、コンストラクターは初期化と内部サブオブジェクトのコンストラクターを呼び出すことにのみ責任を負います。これは、コンパイラが余分なことをしているからです。コンストラクターにコンパイルすると、関数を構築する前に、「@ClassCreate」アセンブリコードの行が挿入されます。 :
関数_ClassCreate(aclass:tclass; alloc:boolean):tobject;
ASM
{ - > eax = vmtへのポインター}
{<-EAX =インスタンスへのポインター}
…
dword ptr [eax] .vmtnewinstance // newinstanceを呼び出します
…
終了
vmtnewinstance = -12;それはクラスのnewinstance関数のオフセットです。
クラス関数newinstance:tobject;
クラス関数tobject.newinstance:tobject;
始める
結果:= initinstance(_getmem(instancesize));
終わり;
「initinstance(_getmem(instancesize))」は3つの関数を順番に呼び出します。
1)最初にinstancesize()を呼び出して、実際のクラスのオブジェクトサイズを返します
クラス関数tobject.instancesize:longint;
始める
結果:= pinteger(integer(self) + vmtinstancesize)^; //実際のクラスのオブジェクトサイズを返します
終わり;
2)_getMem()を呼び出して、ヒープにインスタンスサイズのメモリを割り当て、オブジェクトの参照を返します
3)initinstance()を呼び出して物理データ構造を構築し、整数データメンバーの値を0に設定したり、ポインターをNILに設定したりするなど、メンバーのデフォルト値を設定します。仮想メソッドがある場合は、仮想メソッドテーブルVMTのエントリアドレスをオブジェクトの最初の4バイトに割り当てます。
NewInstanceを呼び出した後、現時点ではオブジェクトには「空のシェル」と実際の「コンテンツ」のみがありません。したがって、オブジェクトを有意に初期化するためにカスタマイズされたコンストラクターを呼び出し、内部サブオブジェクトのコンストラクターを呼び出す必要があります。プログラム内のオブジェクトは、現実の世界のオブジェクトを真に反映しています。これがオブジェクト作成のプロセス全体です。
2.3コンストラクターの代替使用(コンストラクターの多型を実装するためのクラス参照を使用)
Delphiでは、クラスもオブジェクトとして保存されているため、クラスレベルの多型実装を提供するクラス参照と仮想クラスメソッドも実装されています。クラスメソッドを仮想メソッドとして設定し、派生クラスでオーバーライドし、ベースクラスの参照/ポインターを介して呼び出して、実際のクラスを指すクラス参照/ポインターに基づいてオブジェクトが構築されるようにします。次のプログラムをご覧ください。
tmyclass = class
Constructor Create; Virtual;
終わり;
ttmyclass =クラスのtmyclass; //ベースクラスのクラス参照
tmyclasssub = class(tmyclass)
コンストラクターCREATID;
終わり;
手順CreateOBJ(aclass:ttmyclass; var ref);
始める
tobject(ref):= aclass.create;
// refはタイプがなく、任意のタイプと互換性がないため、使用すると明示的にキャストする必要があります(cast)
// Aclassは、クラス参照、統一された関数インターフェイス、および異なる実装です。
// ACLASSによって参照/指された実際のクラスに基づいてオブジェクトを構築します。
終わり;
…
createobj(tmyclass、obj);
createobj(tmyclasssub、subobj);
3つの破壊者と破壊オブジェクト
3.1デストラクタとは(「自然」仮想方法)
意味的に言えば、デストラクタはオブジェクトを破壊し、リソースをリリースする責任があります。デルファイでは、同義語。
Delphiは、Destructorのメソッドタイプも定義します(Mkconstructor、line/source/rtl/common/typinfo.pasを参照してください。 ; VIRTUAL;」VCLクラスのすべての先祖 - なぜVCLはこれを行うのですか?これにより、オブジェクトが多型の状況で正しく破壊されることが保証されるためです。仮想メソッドが使用されない場合、ベースクラスのサブオブジェクトを破壊するだけで、いわゆる「メモリリーク」が生じる可能性があります。したがって、正しいデストラクタを確保するために、破壊者はオーバーライド宣言を追加する必要があります。
3.2オブジェクト破壊のプロセス全体
最初に派生クラスのサブオブジェクトを破壊し、次に基本クラスのサブオブジェクトを破壊します。
ヒント
派生クラスでは、基本クラスのサブオブジェクトは基本クラスから継承された部分を指し、派生クラスの子オブジェクトは新しく追加された部分を指します。
3.3破壊、無料、フリーアンドニル、リリースの使用と違い
破壊:仮想方法
メモリをリリースし、それをTobjectで仮想として宣言し、通常はサブクラスでそれをオーバーライドし、継承されたキーワードを追加して、派生したクラスオブジェクトが正しく破壊されていることを確認します。
しかし、破壊は直接使用できません、なぜですか?
オブジェクトがゼロの場合、まだ破壊を呼び出しますが、エラーが発生します。破壊は仮想方法であるため、オブジェクトの最初の4バイトに基づいて仮想メソッドテーブルVMTの入り口アドレスを見つける必要があるため、破壊の入り口アドレスを見つけるため、この時点でオブジェクトが存在する必要があります。ただし、フリーは、オブジェクトの参照/ポインターのタイプに基づいて決定する必要があります。無料の使用は、破壊を使用するよりも安全です。
2)無料:静的方法
オブジェクトがゼロであるかどうかをテストし、nilではない場合は破壊されます。これが無料のDelphiコードです:
手順tobject.free;
始める
自己<> nilの場合
破壊する;
終わり;
自分の強みと自分の弱点から学ぶのは素晴らしいことではありませんか?
ただし、Calling Destroyはオブジェクトを破壊するだけですが、NILへのオブジェクトの参照は設定されていません。これはプログラマーが行う必要があります。
3)Freeandnil;一般的な方法、非クラス方法。
SysutilsユニットのFreeandnil定義
手順Freeandnil(var obj);
var
温度:tobject;
始める
temp:= tobject(obj);
Pointer(obj):= nil;
Temp.Free;
終わり;
オブジェクトが正しくリリースされるように、無料/破壊の代わりに使用することをお勧めします。
4)TCUSTOMFORMで定義された静的メソッド。
フリー関数は、ウィンドウ内のすべてのイベントが処理された後に呼び出されます。多くの場合、Windowsを破壊するために使用されます。このウィンドウでイベント処理が一定の時間がかかると、この方法では、ウィンドウイベントが処理された後にのみウィンドウが破壊されるようにします。これがtcustomform.releaseのDelphiソースコードです:
手順tcustomform.release;
始める
ポストメサージ(ハンドル、CM_RELEASE、0、0);
// CM_RELEASEメッセージをメッセージキューにウィンドウに送信します。
// CM_RELEASEメッセージ処理プロセスCMRELEASEをもう一度呼び出します
終わり;
CM_RELEASEメッセージ処理のためのCMRELEASEの次の定義を見てみましょう。
手順CMRELEASE(varメッセージ:tmessage);
手順tcustomform.cmrelease;
始める
無料; //最終的には無料です。
終わり;
4 VCL構造と破壊アーキテクチャ
tobject
Constructor Create; //静的メソッド
デストラクタは仮想;
tpersistent
破壊者はオーバーライドします。
tomponent
Constructor create(aowner:tcomponent);
破壊者はオーバーライドします。
tcontrol
Constructor Create(aowner:tomponent);
破壊者はオーバーライドします。
…
以下は、VCLでの構造と破壊のソースコードを分析し、TControlを例にとります。
constructor tcontrol.create(aowner:tcomponent);
始める
継承されたcreate(aowner); //基本クラスのサブオブジェクトを作成し、所有者への破壊権を引き渡します。前に置いてください
//これにより、「最初に基本クラスのサブオブジェクトを作成し、次に派生クラスのサブオブジェクトを作成する」という順序が保証されます。
…//初期化し、内部サブオブジェクトのコンストラクターを呼び出します
終わり;
Destructor TControl.Destroy;
始める
…//派生クラスの内部サブオブジェクトを破壊します
継承された破壊; //基本クラスのオブジェクトを破壊し、最後に置く
//これにより、「派生クラスのサブオブジェクトの最初の破壊、次に基本クラスのサブオブジェクトの破壊」の順序が保証されます。
終わり;
5コンストラクターとデストラクタを正しく使用します
上記の分析の後、以下はコンストラクターとデストラクタを使用する原則を要約しています。
オブジェクトを使用する前に、最初にオブジェクトを作成し、リソースを解放するために時間内にオブジェクトを破壊する必要があります。
2つのオブジェクトが割り当てを参照する場合、表示される名前のないオブジェクト(参照されていないオブジェクトを参照)をリリースできることを確認してください。
コンポーネントを作成する場合は、ホストコンポーネントを設定することをお勧めします(つまり、通常はフォームであるパラメーターを使用します)フォーム/データモジュールの設計と作成コンポーネントのDelphiは、撮影された方法です。そのため、コンポーネントを呼び出すデストラクタを書く必要はありません。
関数の戻りタイプがオブジェクトである場合、結果はオブジェクトへの参照でもあり、結果によって参照されるオブジェクトが存在する必要があることを保証します。
OBJ <> nilまたは割り当て(nil)を使用してオブジェクトが存在することをテストするには、obj:= nilもobj:= nilの後に呼び出す必要があります。
デモプログラムのソースコードを参照してください
指示(推奨)
すべてのDelphiプログラムは、c ++プログラムの場合、win2k+delphi6 SP2で渡されました。この記事の理解を深めるために、デモンストレーションプログラムを参照することをお勧めします。
この記事には、VCL/RTLの学習における私の経験と経験が含まれています。
この記事を読む前に、読者は指向のパスカル言語を特定し、これらの概念のいくつかについてあまり明確でない場合は、関連する記事を参照してください。
この記事を通じて、Delphiのオブジェクトモデル、建設および破壊実装メカニズム、およびVCLの建設と破壊アーキテクチャを理解し、建設および破壊方法の使用を習得できるはずです。 Delphiの構造と破壊は、C ++の構造と破壊よりもはるかに簡単であり、それを習得できるはずです。