MFCからWTLへ(1) ATLの基本

概要 :
WTLの環境について、下記の関連記事で書きました。それでは、WTLを実際に扱ってみたいと思います。
その前に、WTLの仕組み上、ATLについてのある程度の理解が必要です。そのための良い記事(以下の参考記事)がありましたので、それを参考に、少し、ATLの理解を深めたいと思います。
この記事の対象は、MFCのプログラマーでWTLへ興味のある方です。
英語に堪能な方であれば、参照記事を読まれた方がすんなり理解できるかもしれません。(この記事は、内容は、ほぼ似たようなものですが、英訳ではありません。)
ATLの特徴
MFCのプログラマーにとって、ATLの目新しい特徴は、
- テンプレートを利用したより安全に仮想関数を用いている
の点だと、先の記事では、言っていました。私もそのように思います。
さて、その違いは、以下のソースコードの違いでわかると思います。
(このソースコードは、public domainで提供されている参照記事のものを流用しています)
1.MFCのような継承の仕方
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
| #include <stdio.h>
#include <tchar.h>
#include <iostream>
using namespace std;
class B1
{
public:
virtual void SayHi() { PrintClassName(); }
virtual void PrintClassName() { cout << "This is B1."; }
};
class D1 : public B1
{
public:
// No overridden functions at all
};
class D2 : public B1
{
public:
virtual void PrintClassName() { cout << "This is D2."; }
};
int _tmain(int argc, _TCHAR* argv[])
{
D1 d1;
D2 d2;
d1.SayHi(); // prints "This is B1"
d2.SayHi(); // prints "This is D2"
return 0;
}
|
|
2.ATLのような継承の仕方
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
| #include <stdio.h>
#include <tchar.h>
#include <iostream>
using namespace std;
template <class T>
class B1
{
public:
void SayHi()
{
T* pT = static_cast<T*>(this);
pT->PrintClassName();
}
void PrintClassName() { cout << "This is B1."; }
};
class D1 : public B1<D1>
{
public:
// No overridden functions at all
};
class D2 : public B1<D2>
{
public:
void PrintClassName() { cout << "This is D2."; }
};
int _tmain(int argc, _TCHAR* argv[])
{
D1 d1;
D2 d2;
d1.SayHi(); // prints "This is B1"
d2.SayHi(); // prints "This is D2"
return 0;
}
|
|
この出力結果(動作)は、同じです。
上記は、1がMFCっぽい継承で、2がATLっぽい継承です。
1,2の違いは、
- 1は、仮想関数を使っている。2は使っていない。
- 1は、通常のB1->D1,D2への継承。2は、B1を基底としながらも、B1のテンプレートパラメータに、D1,D2自身を使っている。
です。
つまり、2では、テンプレートを使って仮想関数を作成しているのと同じようなことが実現できています。
ということは、2では、実行時の仮想関数テーブルは必要ありませんから、メモリの消費を抑える効果があります。
更に、14行目に
static_castを使っています。
ということは、2では、コンパイル時に型のチェックを行いますから、より安全に扱える効果があります。
この継承か、5重、6重へと引き継がれ、多重継承へと更に複雑になったときを想像してください。
これが、ATL/WTLの軽さの秘密の1つであることが、想像がつくと思います。
参照先の記事では、
"2のテクニックのメリット"として、こう伝えています。
- オブジェクト(つまりそれは、動的に仮想関数を割り当てる)でポインターを必要としません。
- vtblsの必要は全くないので、それだけメモリを節約できます。
- ランタイムで初期化されていないような vtblのNULLポインタを使っての仮想関数のコールは、事実上不可能です。
- すべてのファンクションコールがコンパイル時に決定されているので、それらはすでに最適化できています。
vtbls:仮想関数テーブル
※訳が間違ってたらご一報ください。
ATLのウィンドウを作ってみましょう
では、早速、簡単なダイアログ画面を表示するプログラムをMFCとATLで作成してみましょう。
|
実行すると左記のような画面表示されて、
"OK"、"キャンセル"をクリックすると終了し、
画面右上の"閉じる"をクリックしても終了するように
プログラミングしてみましょう。
|
1.MFCでダイアログを表示
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
| #define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS
#define _AFX_ALL_WARNINGS
#include <afxwin.h>
#include <afxext.h>
#include <afxdtctl.h>
#ifndef _AFX_NO_AFXCMN_SUPPORT
#include <afxcmn.h>
#endif
#include "resource.h"
/////////////////////////////////////////
// The About dialog
class CAboutDlg : public CDialog
{
public:
CAboutDlg() : CDialog(CAboutDlg::IDD) {};
enum { IDD = IDD_ABOUT };
protected:
virtual void DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
}
protected:
DECLARE_MESSAGE_MAP()
};
BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
END_MESSAGE_MAP()
/////////////////////////////////////////
// WinMain
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hInstPrev,
LPSTR szCmdLine, int nCmdShow)
{
if (AfxWinInit(hInst, hInstPrev, szCmdLine, nCmdShow)){
CAboutDlg dlg;
dlg.DoModal();
}
AfxWinTerm();
return 0;
}
|
|
2.ATLでダイアログを表示
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
| #include <atlbase.h>
#if _ATL_VER < 0x7000
extern CComModule _Module;
#endif
#include <atlwin.h>
#if _ATL_VER < 0x7000
CComModule _Module;
#define _APP _Module
#else
#define _APP _AtlBaseModule
#endif
#include "resource.h"
/////////////////////////////////////////
// The About dialog
class CAboutDlg : public CDialogImpl<CAboutDlg>
{
public:
enum { IDD = IDD_ABOUT };
BEGIN_MSG_MAP(CAboutDlg)
MESSAGE_HANDLER(WM_CLOSE, OnClose)
COMMAND_ID_HANDLER(IDOK, OnOKCancel)
COMMAND_ID_HANDLER(IDCANCEL, OnOKCancel)
END_MSG_MAP()
LRESULT OnClose
(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
EndDialog(IDCANCEL);
return 0;
}
LRESULT OnOKCancel
(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
EndDialog(wID);
return 0;
}
};
/////////////////////////////////////////
// WinMain
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hInstPrev,
LPSTR szCmdLine, int nCmdShow)
{
#if _ATL_VER < 0x7000
_Module.Init(NULL, hInst);
#endif
CAboutDlg dlg;
dlg.DoModal();
_APP.Term();
return 0;
}
|
|
簡単に解説しておきます。
ATLのダイアログクラスには、2つあります。
CDialogImpl:単純なダイアログに使用されますが、
CAxDialogImpl:ActiveX コントロールを持っているようなダイアログに使用されます。
ここでは、単純なダイアログですので、CDialogImplを使います。
さて、見た感じ似ていますね。CAboutDlgでの処理に少し違いがあります。
MFCでは、Close,OnOK,OnCacelは、楽ができます。
ATLでは、そのあたりのデフォルト動作はありませんから、全て書く必要があります。
少し、SDKを彷彿させるコーディングです。
また、_ATL_VER がところどころありますが、これは、ATLの基本構造が7.0以降とそれ以前で
異なるために、記述をフラグを使って切り替えています。
全てフリー版で用意したATLであれば、おそらくは、ATLのバージョンは、3.0となります。
様々な面から、3.0では、MFCプログラマーにとって、いらいらする場面が多いと思います。
一番大きな点は、7.0以上でないとATL::CStringが使えないからです。
できれば、7.0以上のバージョンを用意された方が、後々、楽になるかとは思います。
似て非なるところを、次回以降で、もう少し掘り下げたいと思います。
最後に、サイズの比較をしておきましょう。
最適化 /O1 でのリリース版でのサイズ結果
- ATL 56kb
- MFC 29kb(DLL)
- MFC 136kb(STATIC)
おおよそ、予想どおりの結果ではないでしょうか。

結局のところ、WTLは、WindowsAPI+ATLとテンプレートの組み合わせです。
もっと、そのあたりについて詳しく知りたい方は、以下の本なども良いと思います。
はじめて間もない方は、2,3冊読まれると、ネットの記事を読んでも、おおよその理解ができるようになると思います。
本は、経験者でも、ネットだけでは判らない様々な事に気づかされることがあります。
コメントをどうぞ