インターフェイスを実装する:Delphi および C++
C++ の仕様:インデックス への移動
このトピックでは、C++ でインターフェイスを実装する 2 つのメソッドについて説明し、インターフェイスを実装する Delphi メソッドと比較します。 C++ では、インターフェイス実装に 2 つの異なる方法を使用できます。
- 新しい
__property implements
属性 - 継承
__property implements の使用
__property implements
属性により、C++ では、Delphi による 委譲 の利用に似た方法で、より簡単にインターフェイスを実装することができます。詳細については、「C++Builder における __property implements のサポート」を参照してください。
C++ にインターフェイスを実装するときに、IUnknown のメソッドを再実装して転送する必要はありません。 TInterfacedObject から派生させる代わりに、TCppInterfacedObject テンプレートを使用します。 これが IUnknown を処理するので、あとは作成するインターフェイスに固有のメソッドを処理するだけです。
例:
class TMyPersist: public TCppInterfacedObject<IPersist>
{
HRESULT __stdcall GetClassID(CLSID *pClassID)
{
*pClassID = CLSID_SOMEVALUE;
return S_OK;
}
};
Delphi と C++ のインターフェイス実装の比較
実装の例を 2 つ示します。 1 つは Delphi での例、もう 1 つは C++ での例です。 2 つの例は、同じインターフェイスを示します。ただし、Delphi の例は遅延初期化ですが、C++ の例はそうではありません。
Delphi の例
unit Unit1;
interface
type
// Interface that exposes an 'Add' method
IAdder = interface
['{D0C74612-9E4D-459A-9304-FACE27E3577D}']
function Add(I, J: Integer): Integer;
end;
// Aggregatee that implements IAdder
TAdder = class(TAggregatedObject, IAdder)
function Add(I, J: Integer): Integer;
end;
// Aggregator - implements IAdder via TAdder
TPasClass = class(TInterfacedObject, IAdder)
FAdder: TAdder;
function GetAdder: TAdder;
public
destructor Destroy; override;
property Adder: TAdder read GetAdder write FAdder implements IAdder;
end;
function TestAdd(I, J: Integer): Integer;
implementation
{ TAdder }
function TAdder.Add(I, J: Integer): Integer;
begin
Result := I+J;
end;
{ TPasClass }
destructor TPasClass.Destroy;
begin
FAdder.Free;
inherited;
end;
function TPasClass.GetAdder: TAdder;
begin
if FAdder = nil then
FAdder := TAdder.Create(Self as IInterface);
Result := FAdder;
end;
// Adds using TClass' IAdder
function TestAdd(I, J: Integer): Integer;
var
punk: IInterface;
begin
punk := TPasClass.Create as IInterface;
Result := (punk as IAdder).Add(I, J);
end;
end.
C++ の例
C++ の例は、遅延初期化ではありません。
#include <stdio.h>
// Add(..) メソッドを示すインターフェイス
__interface INTERFACE_UUID("{D0C74612-9E4D-459A-9304-FACE27E3577D}") IAdder : public System::IInterface
{
virtual int __fastcall Add(int I, int J) = 0 ;
};
typedef System::DelphiInterface<IAdder> _di_IAdder;
// IAdder を実装する集計対象
class TCppAdder: public TCppAggregatedObject<IAdder> {
public:
__fastcall TCppAdder(const _di_IInterface Controller) : _AGGREGATED_CLASS(Controller)
{}
int __fastcall Add(int i, int j)
{
return i+j;
}
};
// 集計関数 - TCppAdder 経由で IAdder を示す
class TCppClass : public TInterfacedObject {
private:
TCppAdder* obj;
IAdder* FAdder;
protected:
void initAggregatee()
{
_di_IInterface punk;
GetInterface(punk);
_di_IAdder adder;
(obj = new TCppAdder(punk))->GetInterface(adder);
FAdder = adder;
}
public:
__fastcall TCppClass()
{
initAggregatee();
}
__fastcall ~TCppClass()
{
delete obj;
}
__property IAdder* Adder = { read=FAdder, implements };
};
// テスト: TCppClass を使った IAdder.Add(..) の呼び出し
int main()
{
TCppClass* ptr;
_di_IAdder ia;
if (!((ptr = new TCppClass())->GetInterface(ia)))
{
delete ptr;
}
else
{
int result = ia->Add(10, 20);
printf("Add Result = %d\n", result);
}
return 0;
}
継承の使用
次の例は、インターフェイスの実装について Delphi と C++ を比較したものです(継承を使用)。
以下は、インターフェイス IPersist を実装して、単一メソッド GetClassID のみを定義するクラスを作成する Delphi コードです。
Delphi | |
---|---|
Interface |
IPersist = interface(IUnknown)
['0000010C-0000-0000-C000-000000000046']
function GetClassID(out classID: TCLSID): HResult; stdcall;
end;
|
Class |
TMyPersist = class(TInterfacedObject, IPersist)
function GetClassID(out classID: TCLSID): HResult; stdcall;
end;
function TMyPersist.GetClassID(out classID: TCLSID): HResult;
begin
classID := CLSID_SOMEVALUE;
Result := S_OK;
end;
|
比較として、継承を使用する場合に同じクラスを作成する C++ コードを以下に示します。
C++ | |
---|---|
Interface |
struct __declspec(uuid("0000010c-0000-0000-C000-000000000046"))
IPersist : public IUnknown
{
public:
virtual HRESULT __stdcall GetClassID(CLSID *pClassID) = 0;
};
|
Class |
class TMyPersist: public TInterfacedObject, IPersist
{
HRESULT __stdcall GetClassID(CLSID *pClassID)
{
*pClassID = CLSID_SOMEVALUE;
return S_OK;
}
// Methods of IUnknown have to be reimplemented and forwarded :(
HRESULT __stdcall QueryInterface(const GUID& IID, void **Obj)
{ return GetInterface(IID, Obj) ?
S_OK : E_NOINTERFACE; }
ULONG __stdcall AddRef() { return TInterfacedObject::_AddRef(); }
ULONG __stdcall Release() { return TInterfacedObject::_Release(); }
};
|
ActiveX ヘルパ
一般的な ActiveX コントロールは、少なくとも 21 のインターフェイスを実装するため、C++Builder でのインターフェイス実装は、__property implements
属性を使用しない場合には、かなり長くなります。
Delphi ActiveX フレームワーク(DAX)は、親クラス(この場合でいうと、TActiveXControl や TActiveXPropertyPage)がヘルパによって実装されたインターフェイスから直接派生したものではない場合、ヘルパ クラス(TConnectionPoints や TPropertyPageImpl など)を提供します。 Delphi では、ヘルパにマップするプロパティ宣言で implements
指令を使用します。 implements 指令によって、Delphi コンパイラでは、インターフェイスをそのクラスの RTTI InterfaceTable に記録するようになります。
同様に、__property implements
属性により、C++ コンパイラはインターフェイスをそのクラスの RTTI InterfaceTable に記録するようになります。 このテーブルは、IUnknown.QueryInterface がどう実装されるかを示しているため、このテーブルが外部に対して特定のクラスでインターフェイスを実装することを伝えます。
上記の C++ TMyPersist の例では、QueryInterface は TObject.GetInterface を呼び出しているだけであることが分かります。
// Methods of IUnknown have to be reimplemented and forwarded :(
HRESULT __stdcall QueryInterface(const GUID& IID, void **Obj)
{ return GetInterface(IID, Obj) ?
S_OK : E_NOINTERFACE; }
TObject.GetInterface は、クラスのメタデータ内の InterfaceTable から外に出されたものです。 インターフェイスの InterfaceTable エントリがないと、そのクラスはコア TObject にある既存のロジックでこの処理を行う代わりに、QueryInterface の未加工の実装を作成して、実装するそれぞれのインターフェイスを処理しなければならなくなります。 そうした理由から、COM 開発者は、一般には、未加工の IUnknown 実装を作成する代わりに、ATL、MFC、BaseCtl、および DAX などのフレームワークを使用します。
C++ は、__property implements
の何らかのサポートがなければ、DAX が提供する ActiveX ヘルパを活用できません。