IBM SOMの例に関するDelphiの外部オブジェクトシステムの統合

SOM Technology: Making the Pieces Fit 4年前、IBM SOMについての記事が公開されました 。そこでは、非常に嘆かわしい状況、重要なツールが失われ、さらに回復する可能性が低いことに注意しました。 何年もの間、SOM 3.0 for Windows、SOM 2.1、オープンsomFreeクローン、動作するWindows用DirectToSOM C ++コンパイラ、およびOLEオートメーションのブリッジで、多くの出来事が見つかりました。



私のプロジェクトの1つは、 DelphiでSOMサポートを実装しています。 Delphiで開発が始まったので、型チェックを行わずに手続き型で、それほど美しくなく、手作業でバインディングの一部を行わなければなりませんでした。 これらのバインディングを使用して、オブジェクトスタイルのバインディングジェネレーターを作成し、ジェネレーター自体を新しいバインディングに書き換えて、操作性を確認しました。 美しさのために、Delphiオブジェクトシステムをハックする必要がありました。これをどのように行うことができるのか疑問に思うかもしれません。



Uい(「薄い」)バインディングにより、少なくとも何らかの形でライブラリとのやり取りが可能になり、美しい(「厚い」)バインディングは通常のコードの観点から自然になりがちです。 IBM SOM(システムオブジェクトモデル)-オブジェクトについて、オブジェクトはドットを介してメソッドを呼び出します。 オブジェクトスタイルのメソッドを呼び出すことができるDelphiの次のエンティティを知っています。





私は通常、2番目を優先してDelphiまたはAdaで開発しますが、異種コンポーネントをペアリングする技術への関心は、これら2つの言語の友人をさまざまな方法で作成しようとしたことから始まりました。 バインディングの開発の問題において、AdaではなくDelphiでのSOMサポートの始まりは、Delphiで行うことがより困難であるという事実によるものです。 そして、Delphi内で最も難しいことは、型ブロックを作成することであり、インポートツールの開発が始まったのはそれでした。 タイプを打ち負かすことは、主な問題を解決することを意味します。



基本的に、Delphiにはモジュール性があるのは良いことです。 これは、C ++や、特にSOM DTKで提供されるエミッターによって使用されるCの涙よりも優れています。 そして、通常の涙が欠けているところには、大量のマクロが流れ込んでいます。 しかし同時に、Delphiに似たAda言語では、「private with」、「type X;」、「limited with」が登場し、型に関する限られた情報を接続できるようになり、モジュール性とモジュール間の循環接続を行う自由が得られます。同じパッケージ内でループを実行し、この方向でのDelphi開発ではあまりうまく動きませんでした。 さらに悪いことに、メインコンパイラは商用(せいぜい、FPCが1オーダー対Delphiが6オーダーで、Adaはそれに出くわす必要があります。彼は自分自身に出くわしません。Adaどこか落ちたくないです) 7、したがって、お気に入りのAdaにライブラリを記述し、Delphiから使用するために公開したい場合、Delphi 7であることが望ましいため、最後の2つのオプションはクリアされます。 オプションには型制御がなく、メソッドのリストはツールチップにポップアップ表示されません。このオプションも選択解除されています。



当初、文字列やインターフェイスのようにメモリが自動的に制御されるようにRAIIを作成したかったのですが、そのためには、Delphi 2006レコードまたは古いオブジェクトでインターフェイスまたはオプションラップする必要があります 。 このアプローチには重大な欠点があります。 Delphiの規則によれば、クラス、インターフェイス、およびポインターのみを前方に、分割できない型ブロックで宣言できます。 そして今、ContainerクラスとContainedクラスにバインディングを作成し、それらが相互接続されているとしましょう。 Container = record private FSomething:Variant;と書くといいでしょう。 終わり; Containedの場合も同様です。また、メソッドはレコードに投影されたSOMクラスへの参照を受け入れることができ、メソッドがレコード内でのみ記述できる場合、レコードの1つは確実に宣言されないため、所有するメソッドを明確にします



これは、メソッドの引数のためにレコード全体を宣言できないことを意味します。 一部では、最初に各クラスについて、隠されたフィールドを持つがメソッドを持たないオブジェクトの古いシステムからSOMオブジェクトを作成し、それを再び継承し、各メソッドで、メソッドを持たない非継承バージョンを受け入れ、自動的に継承される場合、状況を保存できますメソッド。 しかし、結果には問題があります。反対に、Delphiの制限により、SOMクラスメソッドは、他のバインディングよりも先に作成されたバインディングは、結果として返されたSOMクラスのメソッドで大きくなりすぎた継承バージョンを返すことができません。



ContainerとContainedはやや複雑な関係にあり、お互いを返すのではなく、シーケンス(Korbの動的配列の類似体)を返します。つまり、メソッドで大きくなりすぎていないオブジェクトに対してシーケンスを特別に投影する必要があります。 そして、Delphi XE3でのみ、構造とメソッドの宣言を分離するために、やや堅苦しい方法が登場しました。 まず、レコードのプライベート部分でインターフェイスまたはオプションをラップします。そのため、投影される各クラスに対して、ヘルパーを使用してメソッドをアタッチします。 そして、ヘルパーのこれらのメソッドは、入出に必要なすべてをすでに安全に受け入れることができます。



SOMのメモリ管理を理解して、RAIIを実行しないことにしました。 実際、SOMのIBMバージョンでは、COMおよび最新のObjective-Cのように、すべてのオブジェクトの参照カウンターがありません。オブジェクトへのリンクがコピーされたらどうしますか? ちなみに、AppleにはSOMがあり、そこからsomFreeに切り替えられたので、絶望的ではありませんが、OLEオートメーションにDirectToSOM C ++コンパイラとブリッジもあります。そして、それらはこの動作モード用に設計されていません。



インターフェイスと通常のラッパークラスでは、「オブジェクトへのリンクがコピーされた場合に何をしたいのか」と同様の問題があります。 結局のところ、ラッパーは一時的にSOMオブジェクトを破棄することも、そうでないこともあります。 これは、所有フラグを実行する必要があるラッパーの最小値です。 そして、完全な幸福のために、これすべてに混乱してください。 これは参照カウンタになります。常にプルし、反映せず、混乱しないようにします。 すべてが時計のように機能します。 生きていいだろう。



古いオブジェクトシステムに触れると、警告が流れ始めます。 それが、Delphiオブジェクトシステムをハッキングするという決定に至った理由です。 私のジェネレータは、通常のメソッドを使用してSOMクラスをDelphiクラスに投影します。これらはすべて、Delphi開発者が使い慣れた方法で使用されます。 クラスの場合、遅延宣言が著しく行われるため、後でラップする必要はありません。 すべてのサイクルを1つの型ブロックで閉じる必要があるため、すべてのCORBAモジュールとクラスにネストされたすべての型を1つのDelphiユニットに投影し、このユニットに単一の型ブロックを持たせる必要があります。



DelphiにSOMオブジェクトをDelphiオブジェクトと見なすことを強制することに決めたという事実から始まりました。 DelphiがVMTに触れない限り、すべてが問題ありません。 SOMクラスの各メソッドは、クラスのDelphiメソッドに投影されます。 この場合、SOMメソッドは通常仮想であり、SOMへの呼び出しの送信方法を知っている非仮想のDelphiメソッドに投影されます。 非仮想メソッドはVMTに触れることなく呼び出され、Delphiはそこで処理されているDelphiオブジェクトから遠く離れていることを認識しません。









SOMのメソッドは簡単に呼び出すことができます。 ここには8つの指示(宿題は彼らが何をしているのかを理解しようとすることです)があり、そのうち2つは実際の呼び出しに十分です。 最後の呼び出しの前に、mov / pushは2回行われます。これは呼び出しではなく引数を渡します。 それ以前は、var_14にアドレスを書き込む必要はありません。次に呼び出す必要もありません。最初の命令でアドレスをedxに書き込み、最後に[edx + 1Ch]を使用して呼び出しを行うことができました。 他の開発システムのVMTから仮想メソッドを呼び出すための2つの命令のように、オブジェクトのSOMメソッドを呼び出すための合計2つの命令、さらにマイナス2。 受信したアドレスには、指定されたメソッドを呼び出す最適な方法を知っている動的に作成されたコードフラグメントが含まれており、追加の「隠された」指示を提供しますが、なんと違いがあります。 この違いは、レポート「リリース間バイナリ互換性」翻訳に記載されています。 Delphiの各バージョンが独自のdcuおよびbplセットを持っている理由を理解したい場合、これでおわかりでしょう。



バインディングの生成に戻りましょう。 SOM継承は複数あり、積極的に使用されます。 たとえば、ジェネレーターを開発するとき、OperationDef(メソッドに関するメタ情報)を操作しますが、同時にクラスとその引数のコンテナー内に含まれます。 そして、Delphiクラス-シングル。 理論的には、Delphiでのプロジェクションの最初の親に対して単一の継承を実行し、後で最初のメソッドが追加されたように追加することができます。 それはmeい、非対称の解決策のように思えました。 結局のところ、OperationDef(およびModuleDef、InterfaceDef)は同等にコンテナであり、包含されており、親クラスが宣言されている順序をすぐには覚えていません。 さらに、Delphiクラスの階層の拡大を許可すると、他の問題が発生します。これについては、次の段落で詳しく説明します。 そこで、DelphiでSOMクラスが互いに親にならないように投影しました。 これらはすべて、TObjectメソッドを非表示にするために必要なSOMObjectBaseユーティリティクラスから取得され、その中のメソッドは毎回ゼロから作成されます。 もちろん、「as」および「is」の操作はサポートされていません。SOMオブジェクトのVMTを取得し、それがDelphiオブジェクトのVMTであると考えるためです。 残念ながら、ブロックすることはできませんが、すべてが少なくとも何らかの形で典型化されるように、親クラスごとに、型キャスト用のAs_ ClassNameClass関数が生成され、型キャスト用のサポートクラス関数が生成されます。



熱心な読者は「DelphiがVMTに触れない限り、すべてが問題ない」と書かれていたため、「クラス関数」というフレーズに負担をかける必要がありました。 クラスTObjectメソッドを隠し、独自のものを公開しましたか? 実際、ドロップダウンリストから通常のオブジェクトからSupportsを選択することは可能です。DelphiはVMTに移行します。 エレガントに作成できることがわかりました。 新しいクラスメソッドも非仮想であり、Delphiがオブジェクト上でそれらを呼び出すために行うことは、オブジェクトのゼロオフセットでポインタを取得するだけで、これはクラスメソッドでSelfになります。 また、名前でアクセスしてクラスのクラスメソッドを呼び出す場合、SelfはクラスのVMT Delphiです。 そして、VMT SOMクラスとVMT Delphiクラスを区別する方法は?



しかし、一つの方法があります。 すべてのクラスメソッドは、次のDelphiクラスに毎回再追加されます。Delphiクラスは通常、Delphiツールを使用して継承されるものではなく、Delphiツールを使用して投影されるクラス自体は相互に継承されません。 したがって、SelfではDelphi VMTクラスの可能なバリアントを1つだけ持つことができます。これは同じDelphiクラスのVMTです。それ以外の場合、メソッドはSOMオブジェクトで呼び出され、Selfは実際にはSOM VMTです。 SOM VMTデバイスは不明であり、SOM.DLLのバージョンに依存し、変更される可能性がありますが、オフセットがゼロのオブジェクトクラスへの参照があることがわかっているため、必要です。 SOMでは、すべてのクラスもオブジェクトであり、クラスメソッドは最も一般的な意味でのオブジェクトクラスのメソッドです。 したがって、Selfと自分の名前を比較した後、ClassData構造体のゼロオフセットでSOMクラスへの参照を取得するか、一致しない場合はSelfを逆参照してSOMクラスへのリンクを選択できます。 そして、クラスメソッドが意味することを行います。 実際、このような比較を行うクラスメソッドは2つあります。ClassObjectとNewClassです。2つ目は、ClassData構造に何も見つからなかった場合、クラスが自動的に作成されないという点で異なります。 残りのクラスメソッドは、これら2つのいずれかを呼び出します。 たとえば、そのようなオブジェクトがそのようなクラスの相続人であるかどうかを確認する必要がある場合、そのクラスが作成されていない場合は必要ありません。したがって、noであり、InstanceSizeを要求する場合は、クラスを作成せずに実行できません。 したがって、「o.InstanceSize」(「var o:SOMObject」の場合)と「SOMObject.InstanceSize」の両方が正しく機能します。 Delphiのように。



SOMオブジェクトクラスのすべてのメソッドをDelphiクラスメソッドに投影するというアイデアがありましたが、ここで問題が発見されました。 克服しますが、彼らの克服を放棄することが決定されました。



SOMクラスメソッドをDelphiクラスメソッドに投影する際の問題
まず、Delphiクラスでは動的に作成されませんが、SOMでは-はい。これには、メタクラスがオーバーライドしてトリッキーな動作を実行できるメソッド呼び出しが伴います。 たとえば、いわゆる協調メタクラスは、特定のメソッドの前に独自の実装を挿入できます。これは、クラスがどのように継承されるかに関係なく、常に最初に呼び出され、親メソッドを呼び出すのと同様の方法で制御を通常の実装に転送します。 メタクラスの前/後は、シグネチャに関係なく、すべてのメソッドの前に追加できます。通常のメソッドの前と後の方法で戻る一般的なコード。 たとえば、ミューテックスの開始と終了。 または、エントリのイベントをコンソールに出力し、メソッドを終了します。 リモートプロシージャコールのプロキシも簡単ではありません。 そして、これを可能にするすべてのメソッド、Delphiクラスのクラスメソッドへのダンプは、見苦しい解決策のように思えました。 次に、バインディングはSOM DTKからクラスに作成され、通常はメタクラスを非表示にし、DTK全体で表示されるシングルトンメタクラスのみが表示されます。 第三に、SOMは(人工的な交配まで)子孫のメタクラスがメタクラスの子孫であることを保証し、メタクラスの非互換性の問題はありませんが、これを美しく説明するバインディングのテキストは非常に冗長です。 結局のところ、メタクラスが明示的に指定されている場合にのみ、somGetClassの結果タイプを修正することさえできます。



SOMまたは同様のモデルをサポートする仮想の型付きプログラミング言語コンパイラが、親YおよびZを持つクラスXの特定の子孫を含む変数があり、Yに明示的なメタクラスMYがあり、ZにMZがある場合、およびXにはメタクラスMXが順序付けられていますが、実際には最小の子孫MY、MZ、およびMXがあり、型付きコンパイラーは結果タイプsomGetClassをハックして、すべてのメソッドを備えた「MY&MZ&MX」であり、このクラスのsomGetClassを呼び出して、ユニオンが続行されるようにします はい、バインディングが生成されます。それぞれの潜在的な関連付けを生成するには多すぎます。 そして、それなしで、テキストは多重継承をサポートするために複製されます。 そして、それは、既知のメタクラスを持つSOM DTKクラスの中には、明示的にこれを行ったものだけがありますが、明示的なメタクラスを繰り返さない限り、その子孫はもはや存在しないことを意味します。 したがって、一般的なケースでは、「o.ClassObject。 クラスクラスのオブジェクト 」が、TObjectにあった一部のクラスメソッドについては、それでも便利なアクセスが行われます。



作成し、TLIBIMP.exeの動作に触発されて、クラス関数を作成しました。 Delphiの場合と同様に、「repo:= Repository.Create;」と記述しますが、SOMコンストラクター(SOM用語の初期化子)をDelphiの観点からコンストラクターにするとどうなるのかというアイデアが浮上しました。 そのため、クラスで呼び出されてオブジェクトを作成し、オブジェクトで-メソッドとして機能します。 ここでDelphiクラスをハッキングする方法を示すために、Delphiのオブジェクトがどのように構築および破棄されるかのタイミング図を提供することにしました。



Outer-Create Outer-Create => virtual NewInstance Outer-Create => virtual NewInstance => _GetMem Outer-Create => virtual NewInstance Outer-Create => virtual NewInstance => non-virtual InitInstance Outer-Create => virtual NewInstance => non-virtual InitInstance => FillChar(0) Outer-Create => virtual NewInstance => non-virtual InitInstance Outer-Create => virtual NewInstance Outer-Create Outer-Create => Create Outer-Create Outer-Create => virtual AfterConstruction Outer-Create Free Free => Outer-Destroy Free => Outer-Destroy => virtual BeforeDestruction Free => Outer-Destroy Free => Outer-Destroy => Destroy Free => Outer-Destroy Free => Outer-Destroy => virtual FreeInstance Free => Outer-Destroy => virtual FreeInstance => non-virtual CleanupInstance Free => Outer-Destroy => virtual FreeInstance => non-virtual CleanupInstance => _FinalizeRecord Free => Outer-Destroy => virtual FreeInstance => non-virtual CleanupInstance Free => Outer-Destroy => virtual FreeInstance Free => Outer-Destroy => virtual FreeInstance => _FreeMem Free => Outer-Destroy => virtual FreeInstance Free => Outer-Destroy Free
      
      





Outer-CreateとOuter-Destroyは、コンストラクターとデストラクターを自動的に呼び出すコードです。



SOMに関しては、非標準のコンストラクター(somInitではない)を呼び出す必要がある場合、somNewの代わりにsomNewNoInit関数がクラスオブジェクトで呼び出され、オブジェクトを返します。次に、必要なコンストラクター(somDefaultCopyInitなど)が呼び出されます。 またはすべて同じsomInit。 アイデアは、何らかの方法ですべてのTObjectメソッドをハックして、オブジェクトの作成シーケンスがDelphiの現実で再作成されるようにすることです。 特に、TObject.NewInstanceは仮想クラス関数であることがわかります。 名前を使用したトリックはだまされることはありません。Delphiコンパイラは特定のアドレスのVMTからそれを呼び出します。 ただし、TObjectメソッドが非表示になっているSOMObjectBaseでNewInstanceを非表示にするだけでなく、対応するSOMクラスでsomNewNoInitを呼び出す意味のある実装を提供することもできます。 彼女はこのクラスをどこで取得しますか? たとえば、Delphi VMTを通じて保護された仮想クラス関数を拡張できます。これにより、対応するSOMクラスを自身に返すことができます。 問題は1つだけです。 Outer-Createの最後に、仮想メソッドであるAfterConstructionが呼び出されます。 オブジェクトにすでにSOM VMTがある場合は機能しません。 もちろん、Createの最後にオブジェクトのVMTをSOMからDelphiに一時的に上書きすることも、AfterConstructionでその逆を行うこともできますが、これはある種の酸っぱいスキームです。 だからこの問題で私は退却しなければなりませんでした。



しかし、残りはかなり自然なバインディングであることが判明しました。



Delphiからの継承は実装されていませんが、実装されている場合、美しくするのはやや困難です。 C ++の通常のエミッターを考慮する場合でも、SOMオブジェクトの操作はC ++オブジェクトの操作に似ており、演算子new()および演算子new(void *)はオーバーロードされますが、継承すると、SOMクラスのメソッドの実装はメソッドの実装のように見えませんC ++クラス。 もちろん、特別に修正されたDirectToSOM C ++コンパイラに加えて。



この活動は、独創的なプロジェクトの一環として実施されており、現時点では研究と実証の性格を持っています。 私はAからZまでの落とし穴を知る必要があり、他は基本的な実現可能性を示す必要があります。 どこかで役立つかもしれませんが、SOM、COM、およびObjective-Cの最高の機能を組み込む別の新しいモデルでフィニッシュラインで作業する予定であり、以前のSOM作成者が直面しなかった緊急タスクで作業する準備ができています。



All Articles