2008-08-20

ATLを使ってCOMコードコンポーネントを作る 3

ATLを使ってCOMコードコンポーネントを作る 2 の続き

そろそろお決まりの言い訳を入れておきましょう!
これは私の作業メモですので間違った事が書かれている可能性が大いにあります。

さて、言い訳が終わったところでウィザードで追加した「ATL シンプル オブジェクト」(仮にTestという名前で追加したとします)にメソッドやプロパティを追加してみます。

まず、IDLファイルを見てみると、ウィザードにより次のような記述が追加されています。
  1. インターフェースの定義
    [
    object,
    uuid(XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX),
    dual,
    nonextensible,
    helpstring("ITest インターフェイス"),
    pointer_default(unique)
    ]
    interface ITest : IDispatch{
    };

    ここにある uuid にはウィザードにより適切な値が入っています。ウィザードを使用しないでインターフェースを追加する場合は Visual Studio に付属している uuidgen.exe を使います。
    helpstring で説明を付けておくとIDEで表示されるので簡単な説明を付けておくとよいでしょう。
    その他、dual、nonextensible などはウィザードで選択されたオプションに従い入っています。どんな意味があるのかはヘルプを見ましょう(^^;)

  2. クラスの定義
    library TestLib
    {
    importlib("stdole2.tlb");
    [
    uuid(XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX),
    helpstring("Test Class")
    ]
    coclass Test
    {
    [default] interface ITest;
    };
    };

    こちらにも uuid が登場します。やはりウィザードにより適切な値が入っています。ウィザードを使用しない場合はインターフェースとは別の uuid を発行して使います。
    helpstring はインターフェースと同様適当な説明を付けておきます。
    coclass の部分でTestクラスが実装するインターフェースが定義してあります。


プロパティやメソッドを追加するにはインターフェースの定義に追加する必要があります。
ウィザードを使ってもよいですが、こては手入力の方が速くてよいと思います。
例えばこんな感じに記述します
interface ITest : IDispatch{
[propget, id(1), helpstring("取得、設定が可能なプロパティ")] HRESULT SampleProperty1([out, retval] LONG* pVal);
[propput, id(1), helpstring("取得、設定が可能なプロパティ")] HRESULT SampleProperty1([in] LONG newVal);
[propget, id(2), helpstring("取得のみ可能なプロパティ")] HRESULT SampleProperty2([out, retval] LONG* pVal);
[id(3), helpstring("返値のないメソッド")] HRESULT SampleMethod1([in] LONG arg1);
[id(4), helpstring("返値のあるメソッド")] HRESULT SampleMethod2([in] LONG arg1, [in] DOUBLE arg2, [out,retval] VARIANT_BOOL* result);
[id(5), helpstring("引数から値を返すメソッド")] HRESULT SampleMethod3([in] LONG arg1, [out] LONG* arg2, [out,retval] VARIANT_BOOL* result);
};

プロパティの場合は propget か propput を付けます。プロパティの種類には、たしかもう1種類あったのですが今回は使いません。
id には重複しない値を設定します。id に設定する値には予約された意味のある値(確か -1 など)を付ける事もできます。意味のある id はどこかに定義があるので調べて参照してください。今回は意味のある値は設定せず、1 からの連番を付けます。
helpstring はインターフェースと同様、適当な説明を付けておくとよいでしょう。
以降、id 毎に簡単な説明を記述します。
  1. 取得、設定が可能なプロパティです。この場合は取得、設定で同じ id を付けます。返値は HRESULT とし、プロパティ名には Get や Set は付けません。
    例では引数が1つだけですが、インデックス番号などの引数を追加する事も出来ます。最後の引数は取得側には [out, retval]、設定側には[in] を付け、取得側は設定側と同じ型のポインタ型とします。

  2. 取得のみ可能なプロパティです。設定側のプロパティ定義が無いだけで取得、設定が可能なプロパティと同様に記述します。

  3. 返値のないメソッドです。返値は HRESULT とします。[out, retval] の付いた引数は宣言しません。

  4. 返値のあるメソッドです。返値は HRESULT とします。[out, retval] の付いた引数が返値の宣言です。例では真偽値を返しています。VBなどから使用するために VARIANT_BOOL* 型を使用します。

  5. 引数から値を返すメソッドです。返値は HRESULT とします。[out] の付いた引数が値を返す引数です。例ではLONG値を返しています。

引数で使用している型はCOM用に定義された型です。ULONGなど、VBから使用できない型もあるため注意が必要です。
このようにIDLファイルにインターフェースの定義が出来たらクラス定義に実装コードを追加します。まずはヘッダファイルから
// Test.h : CTest の宣言

#pragma once
#include "resource.h" // メイン シンボル

#include "SampleProject.h"

#if defined(_WIN32_WCE) && !defined(_CE_DCOM) && !defined(_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA)
#error "DCOM の完全サポートを含んでいない Windows Mobile プラットフォームのような Windows CE プラットフォームでは、単一スレッド COM オブジェクトは正しくサポートされていません。ATL が単一スレッド COM オブジェクトの作成をサポートすること、およびその単一スレッド COM オブジェクトの実装の使用を許可することを強制するには、_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA を定義してください。ご使用の rgs ファイルのスレッド モデルは 'Free' に設定されており、DCOM Windows CE 以外のプラットフォームでサポートされる唯一のスレッド モデルと設定されていました。"
#endif



// CTest

class ATL_NO_VTABLE CTest :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CDynamicControl, &CLSID_DynamicControl>,
public ISupportErrorInfo,
public IDispatchImpl<IControlLogic, &IID_IControlLogic, &LIBID_LogicWrapperLib, /*wMajor =*/ 1, /*wMinor =*/ 0>
{
public:
CTest()
{
}

DECLARE_REGISTRY_RESOURCEID(IDR_DYNAMICCONTROL)


BEGIN_COM_MAP(CTest)
COM_INTERFACE_ENTRY(ITest)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(ISupportErrorInfo)
END_COM_MAP()

// ISupportsErrorInfo
STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid);


DECLARE_PROTECT_FINAL_CONSTRUCT()

HRESULT FinalConstruct()
{
return S_OK;
}

void FinalRelease()
{
}

public:

STDMETHOD(get_SampleProperty1)(LONG* pVal);
STDMETHOD(put_SampleProperty1)(LONG newVal);
STDMETHOD(get_SampleProperty2)(LONG* pVal);
STDMETHOD(SampleMethod1)(LONG arg1);
STDMETHOD(SampleMethod2)(LONG arg1, DOUBLE arg2, VARIANT_BOOL* result);
STDMETHOD(SampleMethod3)(LONG arg1, LONG* arg2, VARIANT_BOOL* result);
};

OBJECT_ENTRY_AUTO(__uuidof(Test), CTest)

最後、publicに追加されている6個の関数が追加した部分です。
関数名はIDLの定義と同じにします。プロパティの場合は先頭に get_ または put_ を付けます。
返値などは STDMETHOD マクロにより付くので?気にしなくてよいです。
引数はIDLの定義から [ ] で囲まれた部分を省いたものにします。

次に実装側、cppファイルですが説明が面倒になってきたので簡単に…

宣言の方は STDMETHOD マクロを使用しましたが実装側は何故か使わず、返値は STDMETHODIMP を使用します。
返値としては、処理が成功した場合は S_OK を返します。
失敗した場合は用意されている定義から適当に選んで返します。

IDL で [out] と定義された値には *result = true のようにして値を返します。
本などを読むと [out] と定義された値はメモリを確保して返すように書かれていますが、そのようにするVBに対してとうまく値を返せませんでした。
このあたりは型によって挙動が変わるのかもしれません。

さて、そろそろ本格的に理解できない領域に突入してきましたがもう少しだけ続きます

0 件のコメント: