C++ Windows プログラミング③ 書いて覚えるための初心者自己中記事
前回Windowsデベロッパーセンターの初心者用ページについていけなくなった件。
とりあえず、ぼんやりと理解できたから先に進む。
あ、前回わからなかったのを貼ってから進む。
#ifndef UNICODE #define UNICODE #endif #include <windows.h> template <class DERIVED_TYPE>//ウィンドウに関することをまとめたクラステンプレート/抽象クラスなので継承して使う class BaseWindow { public: static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)//staticなメンバのウィンドウプロシージャ DispatchMessageはここにメッセージを渡す { DERIVED_TYPE *pThis = NULL; if (uMsg == WM_NCCREATE) { CREATESTRUCT* pCreate = (CREATESTRUCT*)lParam; pThis = (DERIVED_TYPE*)pCreate->lpCreateParams; SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pThis);//作成したウィンドウにこのクラス自体が関連付けられる? pThis->m_hwnd = hwnd;//メンバ変数m_hwndに作成したウィンドウのハンドルを登録 } else { pThis = (DERIVED_TYPE*)GetWindowLongPtr(hwnd, GWLP_USERDATA);//ウィンドウ作成済みのメッセージも場合は関連付けられた自身を呼び出す。(メンバ関数が使えるということ?) } if (pThis) { return pThis->HandleMessage(uMsg, wParam, lParam);//メッセージごとの処理はHandleMessageで行う } else { return DefWindowProc(hwnd, uMsg, wParam, lParam); } } BaseWindow() : m_hwnd(NULL) { }//コンストラクタ/メンバ変数m_hwndの初期化 BOOL Create(//ウィンドウの作成を行うメンバ関数/m_hwndにハンドルが入ったかどうかをboolで返す PCWSTR lpWindowName, DWORD dwStyle, DWORD dwExStyle = 0, int x = CW_USEDEFAULT, int y = CW_USEDEFAULT, int nWidth = CW_USEDEFAULT, int nHeight = CW_USEDEFAULT, HWND hWndParent = 0, HMENU hMenu = 0 ) { WNDCLASS wc = { 0 }; wc.lpfnWndProc = DERIVED_TYPE::WindowProc; wc.hInstance = GetModuleHandle(NULL); wc.lpszClassName = ClassName(); RegisterClass(&wc); m_hwnd = CreateWindowEx( dwExStyle, ClassName(), lpWindowName, dwStyle, x, y, nWidth, nHeight, hWndParent, hMenu, GetModuleHandle(NULL), this ); return (m_hwnd ? TRUE : FALSE); } HWND Window() const { return m_hwnd; }//m_hwndに入っているウィンドウハンドルを取得するメンバ関数 protected: virtual PCWSTR ClassName() const = 0;//派生クラスで指定するための純粋仮想関数 virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) = 0;//派生クラスで指定するための純粋仮想関数 HWND m_hwnd;//作成したウィンドウのハンドルを保持するメンバ変数 }; class MainWindow : public BaseWindow<MainWindow>//派生クラスでごとにウィンドウを作れる { public: PCWSTR ClassName() const { return L"Sample Window Class"; }//ウィンドウクラスの名前の設定 LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);//ウィンドウプロシージャに来たメッセージはここに送られてくるのでこっちで処理する }; int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow) { MainWindow win;//抽象クラステンプレート?というのか? の派生クラスを用意して実体化 if (!win.Create(L"Learn to Program Windows", WS_OVERLAPPEDWINDOW))//メンバ関数Createでウィンドウ作成 { return 0; } ShowWindow(win.Window(), nCmdShow);//ShowWindow関数の第一引数に必要なウィンドウハンドルはメンバ関数Windowで取得できる // メッセージ ループを実行する MSG msg = {}; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return 0; } LRESULT MainWindow::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)//ウィンドウプロシージャから渡されたメッセージの処理はここで行う { switch (uMsg) { case WM_DESTROY: PostQuitMessage(0); return 0; case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(m_hwnd, &ps); FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1)); EndPaint(m_hwnd, &ps); } return 0; default: return DefWindowProc(m_hwnd, uMsg, wParam, lParam); } return TRUE; }
よし、Windowsデベロッパーセンターの初心者ページ、解説文でこれは単純って書かなくてもいいじゃん・・。難しいよ・・。
つぎ、WindowsプログラムにおけるCOMの使用
COM(component object model)
全く、知らない。
再利用可能なソフトウェアコンポーネントを作成するための仕様。???
これらはCOMを基盤としている。いや、これらも知らない。汗
えっと、目的はプログラム内でCOMベースのAPIを呼び出すこと、らしい。
COMはバイナリ標準、言語標準ではない。??
COMはアプリケーションとソフトウェアコンポーネント間のバイナリインターフェイスを定義する。
ん~、ソフトウェアコンポーネントがよくわかってないから調べよう。
~~~~~~~~~~~~~
うん、なんだ、再利用可能なプログラムがあってそれらを使うためのインターフェイスがCOMなのか?
進んでみよう。
- オブジェクトの実装とインターフェイスを分離する
- オブジェクトの有効期間を管理する
- オブジェクトの機能を実行時に特定する
これらを勉強するらしい。
大丈夫かな、ついていける気がしない。
COM インターフェイスとは
オブジェクトがサポート可能な一連のメソッドを、実装に関する宣言なしに定義するもの。
インターフェイスによって
メソッドを呼び出すコード
メソッドを実装するコード
の間に明確な境界を設定できる。
???
これを呼び出し元を実装から切り離す、というらしい。
・・・全く分からない。
メソッドを呼び出すコードがアプリケーション側で
メソッドを実装するコードがオブジェクト側ということ?
C++で一番近いのは抽象クラスらしい。
// 以下は実際の COM ではない // Pseudo-C++: interface IDrawable { void Draw(); };
説明のためのやつ。
一部のグラフィックスライブラリのオブジェクトセットが描画可能ということを示している。ということらしい。
IDrawableインターフェイスは、すべての描画可能オブジェクトがサポートすべき操作を定義している。
上記の例ではIDrawableインターフェイスがDrawという操作を定義している。
全てのインターフェイスは抽象的。なので実体は作れない。
代わりにグラフィックスライブラリがIDrawableインターフェイスを実装するオブジェクトを提供する。
C++では共通の抽象型基本クラスを継承する形で行う。
class Shape : public IDrawable { public: virtual void Draw(); // Draw を上書きし、実装を提供する }; class Bitmap : public IDrawable { public: virtual void Draw(); // Draw を上書きし、実装を提供する };
これらを使う場合は直接ポインターを作るのではなく
IDrawable *pDrawable = CreateTriangleShape(); if (pDrawable) { pDrawable->Draw(); }
IDrawableポインター経由でつかう。
おっと、
注: このトピックで例として使われているコードは概念を示すためのものであり、実際に記述することを目的としていません。
まじか。
ねむい・・。
つぎは、やっと実際の話だ。
COMライブラリを初期化する
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
CoInitializeEX関数でCOMライブラリを初期化する。第一引数は予約済みで常にNULL。
第二引数はプログラムが使用するスレッドモデル。
フラグ | 説明 |
---|---|
COINIT_APARTMENTTHREADED | アパートメント スレッド |
COINIT_MULTITHREADED |
マルチスレッド |
どちらかを選択する。
↑ これらの場合はアパートメントスレッド
ウィンドウを作成するスレッドはアパートメント
その他のスレッドはマルチスレッドが一般的らしい。
上記で説明したフラグに加えて、dwCoInit パラメーターに COINIT_DISABLE_OLE1DDE フラグを設定する方法も推奨されます。このフラグを設定することで、現在は使用されなくなった OLE (Object Linking and Embedding) 1.0 テクノロジに伴うオーバーヘッドの一部を回避できます。
なんの事かわからんけど、やっといたほうがいいっぽい。
戻り値の型はHRESULT
エラーコード・成功コードを格納する。
COMを初期化したスレッドが終わるときには初期化解除を行う
CoUninitialize();
つぎ。
COMのエラーコード。
さっきのあれか、HRESULTに格納されるやつ。
HRESULT型は32ビット整数。
最上位ビットが使用されて
0が成功
1が失敗となる。
数値の範囲は下のようになる。
- 成功コード: 0x0–0x7FFFFFFF
- 失敗コード: 0x80000000–0xFFFFFFFF
ん、どう見るんだ?
あ、「-」 は 「~」 か。
COMメソッドにはHRESULT を返さないものがある。
HRESULTの最上位ビットを確認するためマクロがWindows SDKヘッダーに用意されている。
SUCCEEDEマクロ 成功の時にtrue
FAILDEマクロ 失敗の時にtrue
if (SUCCEEDED(hr)) { // 関数が成功する }else { // エラーを処理する }
長くなってきたから、いったん止める。
ここまで。