2011年05月23日
modo501 SDK いじってみた その6 modo501 SP2
今回はCLxIfc_{ }クラスとCLxImpl_{ }クラスの関係を調べて見たい。
modoのプラグインは最初にmodoから初期化されて使えるようになる。それがプラグインのinitialize( )ファンクションだった。その中には基本的に次のようなコードが入っている。
CLxGenericPolymorph *srv;
srv = new CLxPolymorph<ユーザー定義クラス>;
srv->AddInterface (new CLxIfc_〜 <ユーザー定義クラス>);
srv->AddInterface (new CLxIfc_〜 <ユーザー定義クラス>);
:
thisModule.AddServer ("名称", srv);
ユーザー定義クラスをテンプレートにしたCLxPolymorphのインスタンスを生成して、そこにAddInterface( )メソッドで必要なインターフェースを追加して、最後にthisModuleのAddServer( )メソッドでサーバーの名称とその本体をthisModuleに登録した。
この時にサーバーに追加されるインターフェースの鋳型はCLxIfc_〜型のクラスで、テンプレートクラスとしてユーザー定義クラスを渡す。そしてそのユーザー定義クラスでは、インターフェースとして追加されるCLxIfc_〜型のクラスに対応したCLxImpl_〜型のクラスを継承した形で宣言される。
例えば今回調べているspikeyを例に取ると、サーバーが
srv = new CLxPolymorph<CSpikeyTool>;
として生成され、そこに
srv->AddInterface (new CLxIfc_Tool <CSpikeyTool>);
srv->AddInterface (new CLxIfc_ToolModel <CSpikeyTool>);
srv->AddInterface (new CLxIfc_Attributes<CSpikeyTool>);
の3つのインスタンスが追加される。この時ユーザー定義クラスはCSpikeyTool型で、
class CSpikeyTool :
public CLxImpl_Tool,
public CLxImpl_ToolModel,
public CLxDynamicAttributes
CLxIfc_Toolに対してCLxImpl_Toolが、CLxIfc_ToolModelに対してCLxImpl_ToolModel、CLxIfc_Attributesに対して CLxDynamicAttributesがユーザー定義クラスのベースクラスになっている。
CLxImpl_Tool型のクラスについては以前にちょっと触れたけど、
class CLxImpl_Tool {
public:
virtual ~CLxImpl_Tool() {}
virtual void
tool_Reset (void) { }
virtual void
tool_Evaluate (ILxUnknownID vts) { }
virtual LXtObjectID
tool_VectorType (void) = 0;
virtual const char *
tool_Order (void)= 0;
virtual LXtID4
tool_Task (void)= 0;
virtual LxResult
tool_Sequence (ILxUnknownID seq)
{ return LXe_NOTIMPL; }
virtual int
tool_ShouldBeAttribute (LXtID4 task)
{ return 0; }
};
のように仮想関数が並んでいて、継承したユーザー定義クラスで実装する事になっている。
一方CLxImpl_Toolに対するCLxIfc_Toolは
template <class T>
class CLxIfc_Tool : public CLxInterface
{
static void
Reset (LXtObjectID wcom)
{
LXCWxINST (CLxImpl_Tool, loc);
loc->tool_Reset ();
}
static void
Evaluate (LXtObjectID wcom, LXtObjectID vts)
{
LXCWxINST (CLxImpl_Tool, loc);
loc->tool_Evaluate ((ILxUnknownID)vts);
}
:
ILxTool vt;
public:
CLxIfc_Tool ()
{
vt.Reset = Reset;
vt.Evaluate = Evaluate;
vt.VectorType = VectorType;
vt.Order = Order;
vt.Task = Task;
vt.Sequence = Sequence;
vt.ShouldBeAttribute = ShouldBeAttribute;
vTable = &vt.iunk;
lx::GUIDSet (&iid, 0x12E79F81,・・・中略・・・,0x9E );
}
};
となっていて、CLxIfc_ToolはCLxImpl_Toolで定義したメソッドを呼び出すメソッドを有している。modo側からはこれらCLxIfc_Toolのメソッドが見え、これらのメソッドを呼び出すと、間接的にユーザー定義クラス内で実装したメソッドが呼び出されるような仕組みになっているわけだ。
static void Reset (LXtObjectID wcom)を取り上げて調べてみると、中身は
LXCWxINST (CLxImpl_Tool, loc);
loc->tool_Reset ();
だ。LXCWxINSTはマクロでこのように定義されている。
#define LXCWxINST(Y,v) Y *v = static_cast<Y*> (LXCWxWCOM(T))
だからLXCWxINST (CLxImpl_Tool, loc)はマクロ展開すると、
CLxImpl_Tool *loc = static_cast<CLxImpl_Tool *> (LXCWxWCOM(T))
という事になる。さらにLXCWxWCOMもマクロで、
#define LXCWxWCOM(X) LXCWxOBJ(wcom,X)
だから
CLxImpl_Tool *loc = static_cast<CLxImpl_Tool *> (LXCWxOBJ(wcom,T))
となって、LXCWxOBJもマクロで、
#define LXCWxOBJ(p,X) reinterpret_cast<X*>((reinterpret_cast<LXtCOMProxy*>(p))->object)
なので、結局、
CLxImpl_Tool *loc =
static_cast<CLxImpl_Tool *>
(
reinterpret_cast<T*>
(
(
reinterpret_cast<LXtCOMProxy*>
(
wcom
)
)->object
)
)
と展開できる。wcomはResetメソッドの引数でLXtObjectID型だ。これは
typedef void * LXtObjectID;
と定義されているvoid * 型のことだ。その引数で与えられたポインタを強制的に型キャストしてLXtCOMProxy*型と見なしてしまおうってのがreinterpret_cast<LXtCOMProxy*>の部分だ。
LXtCOMProxyの定義はこうなっている。
typedef struct st_LXtCOMProxy {
ILxUnknown *vTable;
LXtCOMProxyID next, prev;
CLxInterface *iFace;
LXtCOMInstanceID instance;
void *object;
unsigned refCount;
} LXtCOMProxy;
だからwcomはこの構造体へのポインタとみなされて、その中のobjectメンバーに
->object
でアクセスする。するとvoid*型のポインタが得られる。これを強制的にT*型に強制的に型キャストして、
reinterpret_cast<T*>
T*型のポインタを得る。これをさらにstatic_castでCLxImpl_Tool *型に型変換キャストをして、
static_cast<CLxImpl_Tool *>
結果的に変数locにwcomが内部に保持していたCLxImpl_Tool 型インスタンスへのポインタが代入されるわけだ。
ところでT型はこのクラスのテンプレートクラスでinitialize()の中で、
srv->AddInterface (new CLxIfc_Tool <CSpikeyTool>);
としているからTはCSpikeyToolに置き換わる。だからメンバー変数objectに入っていたvoid*型のポインタは1度CSpikeyToolクラスのポインタ型へ強制的に型キャストされた上でCLxImpl_Toolクラスのポインタ型に型変換キャストされている事になる。
ここがこのインターフェースの仕組みの最大のミソだ。一見 void* から CLxImpl_Tool* へ変換しちゃっても良さそうに感じるし、実際そう記述してもエラーは出ない(と思う)。しかし実行結果はまるきり違ってくる。reinterpret_castはそのままのメモリーイメージを他の型だと無理やり見なしてしまうだけなのに対してstatic_castは意味的な変換をしてくれる。void * は何の癖も特徴も無い「まっさら」なポインタなのでstatic_castはreinterpret_castと同じように単純にそのメモリーを指定した型に見なすだけしか出来ない。しかし変換対象がCSpikeyTool * で、変換するのがCSpikeyToolの継承元のクラスのCLxImpl_Tool *であれば、static_castは型変換を試みる。その結果CSpikeyToolの中からCLxImpl_Toolの部分が抽出されて変換される。だからCSpikeyToolがいくつものCLxImpl_{ }クラスを多重継承していても、その中でCLxImpl_Toolを実装している部分にちゃんとアクセス出来るようになる。
そしてそのポインタを使って実装部分にアクセスしているのが次の行の
loc->tool_Reset ();
なわけだ。このtool_Reset ()はユーザー定義クラスCSpikeyToolが実装したtool_Reset ()メソッドだ。
もうちょっと単純にしてみるとこんな事になる。
まずベースクラスImpl_tool_A、Impl_tool_BとそのインターフェースクラスIfc_tool_A、Ifc_tool_Bを下のように定義した。Impl_tool_〜クラスにはtool_〜メソッドが仮想メソッドとして定義されている。そしてIfc_tool_〜クラスにはImpl_tool_〜クラス内のメソッドを呼び出すメソッドが定義してある。
#include <stdio.h>
class Impl_tool_A
{
public:
virtual void tool_A(void)=0;
};class Impl_tool_B
{
public:
virtual void tool_B(void)=0;
};template <class T>
class Ifc_tool_A
{
public:
void A(void *tool){
Impl_tool_A *loc = static_cast<Impl_tool_A *>(reinterpret_cast<T *>(tool));
loc->tool_A();
};
};template <class T>
class Ifc_tool_B
{
public:
void B(void *tool){
Impl_tool_B *loc = static_cast<Impl_tool_B *>(reinterpret_cast<T *>(tool));
loc->tool_B();
};
};
ここでImpl_tool_〜クラスを実装したクラスMyTool1とMyTool2を定義する。
class MyTool1 :public Impl_tool_A, public Impl_tool_B
{
public:
void tool_A(void) override {printf("my_tool_A_1\n");}
void tool_B(void) override {printf("my_tool_B_1\n");}
};class MyTool2 :public Impl_tool_B
{
public:
void tool_B(void) override {printf("my_tool_B_2\n");}
};
そしてその実装クラスのインスタンスを作ってvoid*型にキャストする。これをMyTool1とMyTool2をテンプレートにしたIfc_tool_AクラスとIfc_tool_Bクラスのインスタンスを作ってそのメソッドA、Bに渡して実装クラスのメソッドを実行させてみた。
int main(void)
{
void *object1 = reinterpret_cast<void *>(new MyTool1);
void *object2 = reinterpret_cast<void *>(new MyTool2);Ifc_tool_A<MyTool1> a;
Ifc_tool_B<MyTool1> b;
Ifc_tool_A<MyTool2> c;
Ifc_tool_B<MyTool2> d;a.A(object1);
b.B(object1);
// c.A(object2); <-- compile error
d.B(object2);
}
面白いのはIfc_tool_Aはtool_AにアクセスするメソッドAしかなく、MyTool2はtool_Bしかメソッドを持っていないけど、
Ifc_tool_A<MyTool2> c;
という宣言はコンパイルも通るし実行も出来る。しかし
c.A(object2)
はコンパイルエラーになる。
それではまた次回。