はじめに
Windowsアプリケーションを作成するためのC++クラスライブラリといえば、Microsoftが提供するMFCが有名ですが、同じくMicrosoftが提供するライブラリATLを利用することでもWindowsアプリケーションを作成することができます。
本稿では、ATLおよびATLの拡張ライブラリとも言えるWTLを使用したWindowsプログラミングについて説明します。本稿を通してWTLに興味を持っていただけると幸いです。
対象読者
ATL/WTLによるWindowsプログラミングに興味があり、C++やWin32APIによるWindowsプログラミングの基本的な知識がある方。
必要な環境
サンプルはVisual C++ 6.0で作成し、Windows 2000で動作確認しています。
ATLとWTL
ATLとWTLの概要を示します。
ATLとは
「ATL(Active Template Library)」とは、Visual C++ に付属するC++テンプレートライブラリです。主にCOMをサポートするライブラリとして知られていますが、Windowsプログラミングを簡略化するクラスも備えています。そのようなクラスを使用する場合は必ずしもCOMの知識は必要ありません。
WTLとは
「WTL(Windows Template Library)」とは、主にWindowsのGUI部分(コントロール、コモンダイアログ、コマンドバー、ペインコンテナなど)をサポートするC++テンプレートライブラリで、ATLの拡張ライブラリと言えます。元々Microsoftが無料で提供していましたが、2004年5月にオープンソース化されました。ATLと同様、Win32APIをラッパクラスで覆うことにより、Windowsプログラミングを簡略化します。
WTLのインストール
以下にWTLのインストール方法を示します。
WTLの入手
WTLは、オープンソース化される前のバージョン(WTL 7.1)とオープンソース化された後のバージョン(WTL 7.5以降)で、入手場所が異なります。
ATL/WTL AppWizardのインストール
WTLにはAppWizardが付属しています。WTL入手先でダウンロードしたファイルを展開すると「AppWiz」フォルダの中に「setupXX.js」(XXはVisualC++のバージョンを表す数字)ファイルが入っています。このファイルをダブルクリックするとAppWizardファイルがVisual C++の既定のフォルダにコピーされ、プロジェクトの新規作成時にATL/WTL AppWizardのアイコンが表示されるようになります。
Visual C++ 6.0にATL/WTL AppWizardを追加するためには、WTL 7.1の中に入っている「AppWiz60」フォルダの「setup.js」ファイルを実行します。「setup.js」ファイルを実行すると、AppWizardファイルがVisual C++の既定のフォルダにコピーされ、プロジェクトの新規作成時にATL/WTL AppWizardのアイコンが表示されるようになります。
インクルードファイルパスの設定
WTLはC++ヘッダファイルのみで構成されています。どのバージョンのWTLを使う場合でも、Visual C++のインクルードファイルパスにWTLの「include」フォルダパスを追加するだけでインストールは完了です。
ATLウィンドウ
まずはATLのみでウィンドウを作成するサンプルを示します。
ATLにおける最も基本的なウィンドウ関連クラスはCWindow
です。CWindow
クラスはウィンドウハンドルをカプセル化し、ウィンドウに関するWin32APIをラップする多くのメンバ関数を持ちます。
ただし、CWindow
クラスはメッセージへの反応を定義することはできません。ATLではCWindowImpl
クラスから派生クラスを作り、そのクラス内でメッセージへの応答を定義します。以下の示すのは、ウィンドウ中央に「Hello, ATL/WTL」と表示するだけの簡単なプログラムのソースコードです。このプログラムでは、CWindowImpl
クラスから派生クラスCMyWindow
(これがメインウィンドウとなります)を作り、 CMyWindow
クラス内でWM_PAINT
とWM_DESTROY
メッセージへの応答を定義しています。なお、このプログラムはATL/WTL AppWizardを使用せずWin32Applicationプロジェクトでビルドします。
#include <atlbase.h> extern CComModule _Module; #include <atlwin.h>
class CMyWindow : public CWindowImpl<CMyWindow> { public: // ウィンドウクラス名を登録 DECLARE_WND_CLASS(_T("Hello")); private: // メッセージマップ BEGIN_MSG_MAP(CMyWindow) MESSAGE_HANDLER(WM_PAINT, OnPaint) MESSAGE_HANDLER(WM_DESTROY, OnDestroy) END_MSG_MAP() LRESULT OnPaint(UINT, WPARAM, LPARAM, BOOL&){ PAINTSTRUCT ps; HDC hDC = BeginPaint(&ps); RECT rect; GetClientRect(&rect); DrawText(hDC, _T("Hello, ATL/WTL"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER); EndPaint(&ps); return 0; } LRESULT OnDestroy(UINT, WPARAM, LPARAM, BOOL&){ PostQuitMessage(0); return 0; } };
#include "stdafx.h" #include "MainWindow.h" CComModule _Module; int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE, LPTSTR lpCmdLine, int nCmdShow) { _Module.Init(NULL, hInstance); // ウィンドウを作成 CMyWindow wnd; wnd.Create(NULL, CWindow::rcDefault, _T("Hello, ATL/WTL"), WS_OVERLAPPEDWINDOW | WS_VISIBLE); MSG msg; while(GetMessage(&msg, NULL, 0, 0) > 0){ TranslateMessage(&msg); DispatchMessage(&msg); } _Module.Term(); return msg.wParam; }
まず、ATLを使用するためのヘッダをインクルードしますが、CComModule
クラスのインスタンスである _Module
はATLの各ヘッダから参照されるのでグローバルに宣言しておきます。この_Module
は、_tWinMain()
の最初と最後で初期化と後始末をしています。
CMyWindow
クラスはCWindowImpl
クラスから派生していますが、CWindowImpl
の第1テンプレート引数にもCMyWindow
という名前を渡します(第2、3テンプレート引数は省略可能です。ここでは省略しています)。CMyWindow
クラス内では、ウィンドウクラス名をDECLARE_WND_CLASS
マクロによって登録し、メッセージマップによってメッセージとそれに対するハンドラを結びつけています。今回の例では、ハンドラ内の処理はSDKスタイルとほぼ同等です。
ウィンドウクラス情報
SDKスタイルのWindowsプログラミングでは、ウィンドウクラス名や背景色、カーソルなどの属性を指定するためにWNDCLASSEX
構造体を埋めていきますが、 ATLにはその作業を単純化するマクロや構造体が用意されています。
DECLARE_WND_CLASS(ウィンドウクラス名)
DECLARE_WND_CLASS_EX(ウィンドウクラス名, スタイル, 背景色)
CWndClassInfo
構造体
CWndClassInfo
構造体はウィンドウクラス名マクロよりもウィンドウの属性を細かく指定できます。CWndClassInfo
を使用して新しい属性を指定するためには、 CWindowImpl::GetWndClassInfo()
をオーバーライドして、カスタマイズした静的なCWndClassInfo
インスタンスの参照を返します。
class CMyWindow: public CWindowImpl<CMyWindow> { public: static CWndClassInfo& GetWndClassInfo() { static CWndClassInfo wc = { {sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW, StartWindowProc, 0, 0, NULL, NULL, NULL, (HBRUSH)(COLOR_WINDOW + 1), NULL, "MyWindow", NULL}, // WNDCLASSEX構造体 NULL, // 既存のウィンドウクラス名 NULL, // 既存のウィンドウプロシージャ MAKEINTRESOURCE(IDC_CURSOR1), // カーソルリソース名 // システムカーソルならばTRUE、それ以外はFALSE FALSE, 0, // 登録済みウィンドウクラスの識別子 _T("") // ATLが自動生成したウィンドウクラス名 }; return wc; } ... };
ウィンドウ特性
ATLでは、ウィンドウのスタイルは、CWinTraits
クラスによる「ウィンドウ特性」として表現できます。CWinTraits
クラスは、ウィンドウスタイルと拡張ウィンドウスタイルをサポートします。
「Hello, ATL/WTL」プログラムでは、ウィンドウ作成時にCWindowImpl::Create()
の第4引数でウィンドウスタイルを指定しました。この第4引数は省略可能です。省略した場合は、次のように宣言されているCControlWinTraits
クラスがデフォルト値として使用されます。
typedef CWinTraits<WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN |
WS_CLIPSIBLINGS, 0> CControlWinTraits;
CControlWinTraits
クラスは、CWindowImpl
クラスの定義でデフォルトのテンプレート引数として次のように使用されています。
template <class T, class TBase = CWindow, class TWinTraits = CControlWinTraits> class CWindowImpl : public ...
このため、CWindowImpl
クラスの第3テンプレート引数に独自のウィンドウ特性を指定することによって、独自のウィンドウスタイルを指定することも可能です。
typedef CWinTraits<WS_OVERLAPPEDWINDOW | WS_VISIBLE, WS_EX_CLIENTEDGE> CMyTraits; class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CMyTraits> {...};
なお、これらのテンプレート引数によるウィンドウ特性は、CWindowImpl::Create()
の引数によって上書きされます。つまり、CWindowImpl::Create()
で指定したウィンドウスタイルの方が優先されます。
ATLのメッセージマップ
ATLでは、メッセージとそれに対するハンドラ関数をメッセージマップによって結び付けます。
class CMyWindow : public CWindowImpl<CMyWindow> { // メッセージマップ BEGIN_MSG_MAP(CMyWindow) MESSAGE_HANDLER(WM_PAINT, OnPaint) MESSAGE_HANDLER(WM_DESTROY, OnDestroy) END_MSG_MAP() LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled){ // WM_PAINTメッセージへの応答処理 } LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled){ // WM_DESTROYメッセージへの応答処理 } };
ウィンドウがメッセージを受け取ると、メッセージマップの先頭から順に検索されるので、頻繁に使用されるメッセージを最初の方に記述しておくと良いでしょう。メッセージマップ中に対応するメッセージマクロが見つからなければ、メッセージはデフォルトウィンドウプロシージャに渡されます。
ATLが用意するメッセージマクロは大きく分けて3種類あります。それは汎用メッセージハンドラマクロ、コマンドメッセージハンドラマクロ、通知メッセージハンドラマクロの3つです。
汎用メッセージハンドラマクロ
すべてのメッセージを対象とします。汎用メッセージハンドラマクロには次の2つがあります。
MESSAGE_HANDLER(メッセージ名, ハンドラ名)
MESSAGE_RANGE_HANDLER(開始位置のメッセージ名, 終了位置のメッセージ名, ハンドラ名)
// ハンドラ関数名は任意
LRESULT MessageHandler(UINT uMsg, WPARAM wParam,
LPARAM lParam, BOOL& bHandled);
uMsg
はメッセージを識別し、wParam
とlParam
はメッセージパラメータです。メッセージパラメータの内容はメッセージの種類によって変わります。 bHandled
はメッセージの処理を終えたかどうかを示すフラグです。 bHandled
がハンドラ関数の中でFALSE
に設定されていると、メッセージマップの残りの部分で、そのメッセージのためのハンドラが別にないかどうかが検索されます。 bHandled
はハンドラ関数の呼び出し前にTRUE
に設定されるので、ハンドラ関数がbHandled
を明示的にFALSE
に設定しない限り、それ以上のハンドラ検索処理は行われません。
コマンドメッセージハンドラマクロ
コマンドメッセージ(WM_COMMAND
)を対象とします。コマンドメッセージハンドラマクロには次の4つがあります。
COMMAND_HANDLER(コントロールID, 通知コード, ハンドラ名)
COMMAND_ID_HANDLER(コントロールID, ハンドラ名)
COMMAND_CODE_HANDLER(通知コード, ハンドラ名)
COMMAND_RANGE_HANDLER(開始位置のメッセージ名, 終了位置のメッセージ名, ハンドラ名)
// ハンドラ関数名は任意
LRESULT CommandHandler(WORD wNotifyCode, WORD wID,
HWND hWndCtl, BOOL& bHandled);
wNotifyCode
は通知コード、wID
はコマンドを送信しているコントロールの識別子、hWndCtl
はコマンドを送信しているコントロールのハンドル、bHandled
は前述のフラグです。
通知メッセージハンドラマクロ
通知メッセージ(WM_NOTIFY
)を対象とします。通知メッセージハンドラマクロには次の4つがあります。
NOTIFY_HANDLER(コントロールID, 通知コード, ハンドラ名)
NOTIFY_ID_HANDLER(コントロールID, ハンドラ名)
NOTIFY_CODE_HANDLER(通知コード, ハンドラ名)
NOTIFY_RANGE_HANDLER(開始位置のメッセージ名, 終了位置のメッセージ名, ハンドラ名)
// ハンドラ関数名は任意 LRESULT NotifyHandler(int idCtrl, LPNMHDR pnmh, BOOL& bHandled);
idCtrl
は通知を送信しているコントロールの識別子、pnmh
はNMHDR
構造体へのポインタ、 bHandled
は前述のフラグです。