インターフェイスを実装する:Delphi および C++

提供: RAD Studio
移動先: 案内検索

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 の例では、QueryInterfaceTObject.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 ヘルパを活用できません。

関連項目