継承とインターフェイス
VCL に対する言語サポート (C++) への移動
C++ とは異なり、Object Pascal 言語は多重継承をサポートしません。 VCL を上位クラスとして作成されるクラスはすべて、この制限を継承します。 つまり、VCL 形式の C++ クラスには、複数の基底クラスを使用できません。VCL クラスを直接の上位クラスとしない場合も、同様です。
目次 |
多重継承の代わりにインターフェイスを使用する
多くの場合、C++ では多重継承を用いることになる状況で、Object Pascal のコードでは代わりにインターフェイスを利用します。 Object Pascal のインターフェイスの概念に、直接マッピングされる C++ の構造はありません。 Object Pascal のインターフェイスは、実装のないクラスのように動作します。 つまり、インターフェイスとは、すべてのメソッドが純粋仮想で、データ メンバをもたないクラスのようなものです。 Object Pascal のクラスは親クラスを 1 つしか持ちませんが、インターフェイスはいくつでもサポートできます。 Object Pascal のコードでは、クラス インスタンスをどのインターフェイス型の変数にも代入できます。同様に、クラス インスタンスはどの上位クラス型の変数にも代入できます。 これにより、共通の上位クラスを持たなくても、同じインターフェイスを共有するクラスの多態的(ポリモーフィック)な動作が実現できます。
C++Builder のコンパイラは、純粋仮想メソッドのみを持ち、データ メンバを持たないクラスを、Object Pascal のインターフェイスに相当すると認識します。 そのため、VCL 形式のクラスを作成するときは多重継承を使用できますが、VCL(つまり VCL 形式のクラス)そのものを除くすべての基底クラスがデータ メンバを持たず、純粋仮想メソッドのみを持つ場合だけです。
メモ:インターフェイス クラスは VCL 形式のクラスである必要はありません。唯一の要件は、データ メンバを持たず、純粋仮想メソッドのみを持つことです。
インターフェイス クラスを宣言する
インターフェイスを表すクラスは、他の C++ のクラスと同じように宣言できます。 ただし、特定の規約をいくつか使用すると、そのクラスをインターフェイスとして動作させることを、明示できます。 これらの規約を次に示します。
- class キーワードの代わりに、__interface を使用してインターフェイスを宣言します。 __interface は、class キーワードをマッピングするマクロです。 これは必須ではありませんが、そのクラスはインターフェイスとして動作することが意図されている、と明示します。
- インターフェイスの名前は通常、'I' で始めます(IComponentEditor、IDesigner など)。 この規約に従えば、クラスがインターフェイスとして動作するかどうか確認するのに、そのクラスの宣言を見直す必要がありません。
- 通常、インターフェイスには GUID を関連付けます。 これは絶対要件ではありませんが、インターフェイスをサポートするコードの多くは、GUID を検出できることを前提としています。 uuid 引数の __declspec 修飾子を用いると、インターフェイスと GUID を関連付けることができます。 たとえば、INTERFACE_UUID マクロは、同じことをマッピングしています。
これらの規約に従ったインターフェイス宣言を次に示します。
__interface INTERFACE_UUID("{C527B88F-3F8E-1134-80e0-01A04F57B270}") IHelloWorld : public IInterface
{
public :
virtual void __stdcall SayHelloWorld( void ) = 0 ;
};
通常、インターフェイス クラスを宣言すると、C++Builder のコードでは、対応する DelphiInterface クラスも宣言されます。DelphiInterface クラスは、インターフェイスをより使いやすくします。
typedef System::DelphiInterface< IHelloWorld > _di_IHelloWorld;
IUnknown と IInterface
Object Pascal のすべてのインターフェイスは、共通の祖先 IInterface から派生します。 VCL 形式のクラスが C++ のインターフェイス クラスを追加の基底クラスとして使用できるという意味では、C++ のインターフェイス クラスが IInterface を基底クラスとして使用する必要はありません。ただし、インターフェイスを扱う VCL のコードは、IInterface メソッドがあることを前提としています。
COM プログラミングでは、すべてのインターフェイスが IUnknown から派生します。 VCL の COM サポートは、IInterface を直接マッピングする IUnknown の定義に基づいています。 つまり、Object Pascal では、IUnknown と IInterface は同じものです。
ただし、Object Pascal の IUnknown 定義は、C++Builder で使用される IUnknown の定義に相当しません。 ファイル unknwn.h では、IUnknown が定義され、次の 3 つのメソッドを含みます。
virtual HRESULT STDCALL QueryInterface( const GUID &guid, void ** ppv ) = 0 ; virtual ULONG STDCALL AddRef() = 0 ; virtual ULONG STDCALL Release() = 0 ;
これは、COM 仕様の一部として Microsoft が定義する、IUnknown の定義に相当します。
Object Pascal の場合とは異なり、C++Builder の IInterface 定義と IUnknown 定義は、同一のものではありません。 代わりに、IInterface は、追加メソッド Supprts を含む IUnknown から派生します。
template <typename T >
HRESULT __stdcall Supports(DelphiInterface<T>& smartIntf )
{
return QueryInterface( __uuidof (T) , reinterpret_cast < void **>( static_cast <T**>(&smartIntf))) ; }
Supports を使用すると、IInterface を実装するオブジェクトから、サポートされる他のインターフェイスの DelphiInterface を取得できます。 そのため、たとえば、IMyFirstInterface というインターフェイスが DelphiInterface を持ち、実装するオブジェクトも IMySecondInterface(このインターフェイスの DelphiInterface 型は _di_IMySecondInterface)を実装している場合、次のように、2 つ目のインターフェイスの DelphiInterface を取得できます。
_di_IMySecondInterface MySecondIntf ; MyFirstIntf->Supports(MySecondIntf) ;
VCL は、Object Pascal で IUnknown のマッピングを使用します。 このマッピングにより、IUnknown メソッドで使用される型が Object Pascal の型に変換され、AddRef メソッドと Release メソッドの名前が _AddRef と _Release に変更されます。これは、これらのメソッドが直接呼び出されるべきではないと示すためです。 (Object Pascal では、コンパイラが自動的に、IUnknown メソッドに必要な呼び出しを生成します。) VCL オブジェクトがサポートする IUnknown(または IInterface)メソッドが、C++ にマッピングされ直すと、次のメソッド シグニチャになります。
virtual HRESULT __stdcall QueryInterface( const GUID &IID, void *Obj) ; int __stdcall _AddRef( void ) ; int __stdcall _Release( void ) ;
つまり、Object Pascal で IUnknown や IInterface をサポートする VCL オブジェクトは、C++Builder の IUnknown や IInterface のバージョンはサポートしません。
IUnknown をサポートするクラスを作成する
VCL のクラスの多くは、インターフェイス サポートを含みます。 事実、VCL 形式のすべてのクラスの基底クラスである TObject は、GetInterface メソッドを持ちます。このメソッドは、オブジェクト インスタンスがサポートするすべてのインターフェイスの DelphiInterface クラスを取得できます。 TComponent と TInterfacedObject は共に、IInterface(IUnknown)メソッドを実装します。 このため、作成する TComponent や TInterfacedObject の下位クラスは、Object Pascal のすべてのインターフェイスに共通なメソッドのサポートを自動的に継承します。 Object Pascal では、これらのクラスの下位クラスを作成すると、新しいインターフェイスが導入したこれらのメソッドを実装するだけで、継承した IInterface メソッドのデフォルトの実装に応じて、新しいインターフェイスをサポートできます。 残念ながら、IUnknown メソッドと IInterface メソッドのシグニチャが、C++Builder と VCL クラスの各バージョンでは異なるので、作成する VCL 形式のクラスは IInterface や IUnknown のサポートを自動的には含みません。(直接または、間接的に)TComponent や TInterfacedObject から派生させた場合も同様です。 つまり今でも、IUnknown や IInterface から派生させて C++ で定義したインターフェイスをサポートするには、IUnknown メソッドの実装を追加する必要があります。
TComponent や TInterfacedObject から派生したクラスで IUnknown メソッドを実装する、最も簡単な方法は、関数のシグニチャは異なりますが、組み込みの IUnknown サポートを利用することです。 継承された Object Pascal ベースのバージョンをデリゲートする、C++ バージョンの IUnknown メソッドの実装を追加するだけです。 以下はその例です。
virtual HRESULT __stdcall QueryInterface( const GUID& IID, void **Obj)
{
return TInterfacedObject::QueryInterface(IID, ( void *)Obj);
}
virtual ULONG __stdcall AddRef( )
{
return TInterfacedObject::_AddRef() ;
}
...
virtual ULONG __stdcall Release()
{
return TInterfacedObject::_Release();
}
上述の 3 つのメソッド実装を TInterfacedObject の下位クラスに追加すると、そのクラスは IUnknown または IInterface を完全にサポートします。
インターフェイス化したクラスと存続時間管理
インターフェイス クラスの IUnknown メソッドは、そのインターフェイスを実装するオブジェクトを割り当てたり解放する方法に影響を与えます。 COM オブジェクトで IUnknown を実装した場合、そのオブジェクトは IUnknown メソッドを使用して、使用されている、そのインターフェイスへの参照の数を追跡します。 参照カウントが 0 になったときに、オブジェクトは自動的に自身を解放します。 TInterfacedObject は IUnknown メソッドを実装して、これと同様の存続時間管理を行います。
ただし、このような IUnknown メソッドの解釈は、必ずしも必要ではありません。 たとえば、TComponent が提供する IUnknown メソッドのデフォルトの実装は、インターフェイスの参照カウントを無視するため、このコンポーネントは参照カウントが 0 になっても自身を解放しません。 TComponent は、自身の Owner プロパティで指定するオブジェクトに、解放されるからです。
これら 2 つのモデルのハイブリッドを使用するコンポーネントもあります。 それらは、Owner プロパティが NULL の場合、存続時間管理にインターフェイスの参照カウントを使用して、参照カウントが 0 になったときに自身を解放します。 所有者がある場合は、参照カウントを無視して、所有者が解放できるようにします。 このようなハイブリッド オブジェクトは、アプリケーションがそのオブジェクトを作成しても、そこからインターフェイスを 1 つも取得しなかった場合は、自動的に開放されないことに注意してください。これは、存続時間管理に参照カウントを利用する他のオブジェクトも同様です。