[c++] __event と __hook を使って mvvm を実装してみる

__eventを使ったイベントハンドリング – CREST’S WEBLOG (」・ω・)」うー!(/・ω・)/にゃー!
http://d.hatena.ne.jp/Crest/20100418/1271603367
__event
http://msdn.microsoft.com/en-us/library/cb1dzt8t(v=vs.100).aspx

MS-C++ では __declspec(property()) でプロパティを作れるよ | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/3557

を書いてみて、ひょっとすると event/delegate 系も使えるようになっているのでは?と思ったらありました。
__event/__hook の組み合わせでいけます。C# の event/delegate, Action<>と機能は同じ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#include "stdafx.h"
#include <afxwin.h>
#include <iostream>
#include <string>
using namespace std;
 
// Model側のインターフェース(INotify)
class INotifyProperyChange
{
public:
    __event void PropertyChange( INotifyProperyChange *model, void *name, void *value, const type_info &info );
};
 
// View側のインターフェース(ITarget)
class ITargetPropertyChanged
{
public:
    void OnProperyChanged( INotifyProperyChange *model, void *name, void *value, const type_info &info );
};
 
class Model : public INotifyProperyChange
{
private:
    int m_num;
    string m_name;
public:
 
    __declspec(property(get=getNum, put=setNum)) int Num;
    int getNum() { return m_num ; }
    void setNum( int value ) {
        m_num = value;
        PropertyChange( this, "Num", &value, typeid(value) );
    }
 
    __declspec(property(get=getName,put=setName)) string Name;
    string &getName() { return m_name; }
    void setName( string &value ) {
        m_name = value;
        PropertyChange( this, "Name", &value, typeid(value) );
    }
 
};
 
// View は Model から独立している
class View : public ITargetPropertyChanged
{
private:
    int m_num;
    string m_name;
public:
    __declspec(property(get=getNum, put=setNum)) int Num;
    int getNum() { return m_num; }
    void setNum( int value ) {
        m_num = value;
        cout << "View::Num: " << value << endl;
    }
    __declspec(property(get=getName,put=setName)) string Name;
    string &getName() { return m_name; }
    void setName( string &value ) {
        m_name = value;
        cout << "View::Name: " << value << endl;
    }
public:
    void OnProperyChanged( INotifyProperyChange *model, void *name, void *value, const type_info &info )
    {
        // ここを map にする?
        string propName((const char*)name);
        if ( propName == string("Num")) {
            this->Num = *(int*)value;
        } else if ( propName == string("Name")) {
            this->Name = *(string*)value;
        }
    }
};
 
// ViewModel は View と Model の両方に依存しているが、
// View のほうへの依存度が高い
class ViewModel
{
private:
    View *m_view;
    Model *m_model;
public:
    void OnProperyChanged( INotifyProperyChange *model, void *name, void *value, const type_info &info )
    {
        m_view->OnProperyChanged( model, name, value, info );
    }
    void Bind( Model *model, View *view )
    {
        this->m_model = model;
        this->m_view = view;
        __hook( &Model::PropertyChange, model, &ViewModel::OnProperyChanged, view );
    }
};
 
int _tmain(int argc, _TCHAR* argv[])
{
    Model model;
    View view;
    ViewModel vm;
 
    vm.Bind( &model, &view );
    model.Num = 10;
    model.Name = string("masuda tomoaki");
}
1
2
3
D:\work\blog\src\CppProperty\Debug>CppProperty.exe
View::Num: 10
View::Name: masuda tomoaki

ざっと書くとこんな感じ。C# の一般的な mvvm よりも、Model と View の独立性を強くしてあります。__hook の利用法が面白いところで、__hook( ソースの関数ポインタ、ソースオブジェクト、ターゲットの関数ポインタ) を設定します。ここで仕様バグなのかどうかは不明ですが、ターゲットの関数ポインタは、何も自分のクラスのメソッドである必要はありません。ここでは &ViewModel::OnProperyChanged と自分自身を示していますが、&View::OnProperyChanged のほうに他クラスの関数ポインタも示せるという優れもの…と言いますかいい加減な実装がw。ソースの関数メソッドとターゲットの関数メソッドの「型があっていればOK」といういい加減な実装(意図的?)なために、かなり自由度が高いのです。

これを試しに interface だけ(class継承だけ)を使うと、どうしてもうまく行きません。関数ポインタの型がきつくて、一度 static 関数を通さないと実装ができないという状態なので、__hook は重宝します。
まあ、考えてみれば、c/c++ なのだからインラインアセンブラを作れば良いのでした。なるほど。

さて、これでコマンドラインでは、c++ で(おそらく ms-c 限定ですが) mvvm の INotiry, ITarget が動くことが分かりました。今度は windows form(内部的には *.rc)を使って実装してみましょうか。DDX_Control マクロと UpdateData 関数を使わずに実装できるかも。

そうそう View::OnProperyChanged の実装がいまいちなので、View のプロパティ名=バインド名の部分をなんとかしたいですね。C# ならばリフレクションを使うところですが、C++ の場合はどうするか思案中。

カテゴリー: C++ パーマリンク