テキストファイルの書き込みにはStreamWriterを用いる。System::Text::Encodingでエンコーディングの指定が可能
void FileWrite(System::String^ filepathname) { System::IO::StreamWriter^ sw = gcnew System::IO::StreamWriter( filepathname, //ファイル名 false, //追記モード System::Text::Encoding::UTF8 //System::Text::Encoding::GetEncoding("shift_jis") //文字コード ); sw->Write("吾輩は猫である。名前はまだ無い。\n"); sw->Write("どこで生れたかとんと見当がつかぬ。\n"); sw->Write("何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。\n"); sw->Write("吾輩はここで始めて人間というものを見た。\n"); sw->Write("しかもあとで聞くとそれは書生という人間中で一番獰悪な種族であったそうだ。\n"); sw->Write("この書生というのは時々我々を捕えて煮て食うという話である。\n"); sw->Close(); }
テキストファイルの読み込みにはStreamReaderを使用する。
一行ずつの読み込みにはReadLineを用いる。ファイル終端判定はPeekを使うのが一般的のようだが、参考文献によると模範解答ではないらしい。ここではEndOfStreamを使っている
void FileRead(System::String^ filepathname) { //"C:\test\1.txt"をShift-JISコードとして開く System::IO::StreamReader^ sr = gcnew System::IO::StreamReader( "C:\\test\\sample.txt", System::Text::Encoding::Encoding::UTF8); while(sr->EndOfStream != true ){ System::String^ r = sr->ReadLine(); System::Console::WriteLine( r );//ReadLineは改行を含まない } sr->Close(); }
int main(array<System::String ^> ^args){ FileWrite("C:\\test\\sample.txt"); FileRead("C:\\test\\sample.txt"); System::Console::Read(); }
参考文献
https://dobon.net/vb/dotnet/file/readfile.html
グローバルフックをするには、DLLにする必要がある。
また、イベントがあったことを知るために、FindWindowで送信先のウィンドウクラス名を指定してSendMessageしている。
マウスフックの時は、SetWindowsHookExに渡す引数はWH_MOUSEとなる。また、フックプロシージャのwParamにはイベントが入っている。
#pragma once #include <Windows.h> BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ); HINSTANCE GetDllInstance();
HINSTANCE hDLLInstance = nullptr; BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { hDLLInstance = hModule; return TRUE; } HINSTANCE GetDllInstance() { return hDLLInstance; }
フックをかけるときにインスタンスハンドルが必要になるので、DllMainの中で保存しておく。
DLL側にはプリプロセッサにDLL_EXPORT_DOをセットする。
#ifdef DLL_EXPORT_DO /* DLLを作る場合 */ #define __DLL_PORT extern "C" __declspec(dllexport) #define __DLL_PORT_CLS __declspec(dllexport) #else /* DLLを使う場合 */ #define __DLL_PORT extern "C" __declspec(dllimport) #define __DLL_PORT_CLS __declspec(dllimport) #endif
このファイルは次のMouseHook.hでincludeされるが、結果的にexe側でもincludeされることになるので、両方のプロジェクトから読める必要がある。
#pragma once #include <Windows.h> #include "../DLLDefs.h" //マウスがクリックされたら、本体のプログラムへこのメッセージを送信する constexpr UINT WM_HOOKED_MOUSEDOWN = WM_USER + 2; class __DLL_PORT_CLS CMouseHook { static HHOOK hHook; static LRESULT CALLBACK MouseProcedure(int p_nCode, WPARAM p_wParam, LPARAM p_lParam); public: BOOL Set(); BOOL Unset(); ~CMouseHook(); };
フック関数とフックハンドルはstaticにする。
#include "MouseHook.h" #include "dllmain.h" #include <tchar.h> #pragma warning(disable:4996) #pragma data_seg( ".CMouseHookHandle" ) HHOOK CMouseHook::hHook = nullptr; #pragma data_seg() BOOL CMouseHook::Set() {//フックをセットする関数 hHook = ::SetWindowsHookEx(WH_MOUSE //種類はマウスフック , (HOOKPROC)CMouseHook::MouseProcedure , GetDllInstance(), 0);//このDLLのインスタンスハンドルを指定 if (!hHook) return FALSE; return TRUE; } BOOL CMouseHook::Unset() {//フックを外す BOOL ret = FALSE; if (hHook) ret = ::UnhookWindowsHookEx(hHook); hHook = nullptr; return ret; } //フックの処理 LRESULT CALLBACK CMouseHook::MouseProcedure(int p_nCode, WPARAM p_wParam, LPARAM p_lParam) { if (p_nCode < 0) return ::CallNextHookEx(hHook, p_nCode, p_wParam, p_lParam); if (p_nCode == HC_ACTION) { MOUSEHOOKSTRUCT *pmh = (MOUSEHOOKSTRUCT *)p_lParam; if (pmh) { if (p_wParam == WM_LBUTTONDOWN || p_wParam == WM_NCLBUTTONDOWN) { //このNU_WND_CLASS_NAMEは、exe側で作成するウィンドウのウィンドウクラス名 PostMessage(FindWindow(_T("NU_WND_CLASS_NAME"), nullptr), WM_HOOKED_MOUSEDOWN, p_wParam, p_lParam); //exe側にWM_HOOKED_MOUSEDOWNを送る } } } return ::CallNextHookEx(hHook, p_nCode, p_wParam, p_lParam);; } CMouseHook::~CMouseHook() { Unset(); }
SECTIONS .CMouseHookHandle READ WRITE SHARED
#pragma comment(lib, "MouseHook.lib") #include "../MouseHook/MouseHook.h" #include<windows.h> #include <tchar.h> //ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_DESTROY: DestroyWindow(hwnd); PostQuitMessage(0); return 0; case WM_HOOKED_MOUSEDOWN: OutputDebugString(_T("どこかの窓でマウスがクリックされました\n")); return 0; } return DefWindowProc(hwnd, msg, wp, lp); } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow) { HWND hwnd; MSG msg; WNDCLASS winc; //ウィンドウクラスの登録 winc.style = CS_HREDRAW | CS_VREDRAW; winc.lpfnWndProc = WndProc; winc.cbClsExtra = winc.cbWndExtra = 0; winc.hInstance = hInstance; winc.hIcon = LoadIcon(NULL, IDI_APPLICATION); winc.hCursor = LoadCursor(NULL, IDC_ARROW); winc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); winc.lpszMenuName = NULL; winc.lpszClassName = TEXT("NU_WND_CLASS_NAME"); if (!RegisterClass(&winc)) return 0; //フックをかける CMouseHook hm; hm.Set(); //ウィンドウ作成 hwnd = CreateWindow( TEXT("NU_WND_CLASS_NAME"), TEXT("test"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 200, 200, NULL, NULL, hInstance, NULL ); if (hwnd == NULL) return 0; while (GetMessage(&msg, NULL, 0, 0)) DispatchMessage(&msg); //フックを外す hm.Unset(); return msg.wParam; }
VRAY for Blenderでfogを使う方法。
オブジェクトのマテリアルではなく、Worldの方のEffectsにフォグを設定し、「FogをObjectの中限定で発生させる」という考え方をするらしい。
複数のオブジェクトをフォグにする場合、Effects ContainerにAddを使い、二つ以上の設定を束ねてWorld Outputへ接続する。
CUDAをやってみたくなった。
インストールがもの凄く簡単になっていて、Visual C++ 2017 Communityが入っているなら
https://developer.nvidia.com/cuda-downloads
からCUDA Toolkitをダウンロードしてインストールするだけで、VC++のプロジェクトにCUDAのプロジェクトが追加される。

以下、初めてのCUDAプログラム
#include "cuda_runtime.h" #include "device_launch_parameters.h" #include <stdio.h> #include <cstring> __global__ void hello(char *c) { c[0] = 'h'; c[1] = 'e'; c[2] = 'l'; c[3] = 'l'; c[4] = 'o'; c[5] = '\0'; } int main(void) { const size_t LEN = strlen("hello") + 1;//(CPU側) char c_cpu[LEN];//(CPU側) char *c_gpu;//(CPU側) cudaMalloc( (void**)&c_gpu, LEN);//GPU側にメモリを確保 hello <<<1, 1 >>>( c_gpu );//GPU側の関数を呼出 cudaMemcpy(&c_cpu, c_gpu, LEN, cudaMemcpyDeviceToHost);//GPU側から実行結果を取得 cudaFree(c_gpu);//GPU側のメモリを解放 puts(c_cpu);//(CPU側) getchar();//(CPU側) return 0; }
cudaMalloc
GPU側のメモリを確保するための関数。
| 関数名 | cudaMalloc | ||||||
| 戻り値 | cudaError_t | ||||||
| 引数 |
|
参考URI:
cudaFree
cudaMallocで確保したメモリを開放する
| 関数名 | cudaFree | |||
| 戻り値 | cudaError_t | |||
|
参考URI
特に解説することはない。
ref class CTest{ public: int func(double v){ return (int)v * (int)v; } delegate int DelegateToFuncT(double v);//デリゲート型を定義 }; /////////////////////////////////////////////////// int main(array<System::String ^> ^args) { CTest^ ct = gcnew CTest(); //デリゲート作成 CTest::DelegateToFuncT^ dfunc = gcnew CTest::DelegateToFuncT(ct,&CTest::func); int ret = dfunc( 5.5 );//呼出 Console::WriteLine( System::String::Format("** {0}\n",ret) ); Console::ReadLine(); return 0; }
#pragma onceを忘れたとかそんな話ではない。
すべてCLIで、testclidll.dll内にクラスClass1を作成し、それをexe側に参照を追加してClass1を使おうとしたとき、
error C2011: 'testclidll::Class1' : 'class' 型の再定義
が発生する
再現手順 (Visual Studio 2017 community)
1.[ファイル] - [新規作成] - [プロジェクト] から、「CLRコンソールアプリケーション」を作成(名前をexe_and_dll_testとする)
2.[ファイル] - [新規作成] - [プロジェクト] から、「クラスライブラリ」を作成(名前をtestclidllとする)
3.testclidll.hを以下のようにする
// testclidll.h #pragma once using namespace System; namespace testclidll { public ref class Class1 { int c; public: void Set(int d); int Get(); }; }
4.testclidll.cppを以下のようにする
namespace testclidll { void Class1::Set(int d){ c = d; } int Class1::Get(){ return c; } }
5.exe側を以下のようにする
#include "stdafx.h" using namespace System; #include "../testclidll/testclidll.h" int main(array<System::String ^> ^args) { testclidll::Class1^ c = gcnew testclidll::Class1; c->Set(10); Console::WriteLine(System::String::Format("{0}\n",c->Get())); return 0; }
6.DLLをコンパイルする
7.ソリューションエクスプローラから、exe側のプロジェクトを右クリックし、[参照...]を選択
プロパティページから、[共通プロパティ]-[Frameworkと参照]で「新しい参照の追加...」ボタンを押し、コンパイルしたdllを追加する
8.exe側をコンパイルする
2>e:\mycodes\exe_and_dll_test\exe_and_dll_test\../testclidll/testclidll.h(12): error C2011: 'testclidll::Class1' : 'class' 型の再定義
2> e:\mycodes\exe_and_dll_test\debug\testclidll.dll : 'testclidll::Class1' の宣言を確認してください。
2>exe_and_dll_test.cpp(14): error C2027: 認識できない型 'testclidll::Class1' が使われています。
2> e:\mycodes\exe_and_dll_test\debug\testclidll.dll : 'testclidll::Class1' の宣言を確認してください。
2>exe_and_dll_test.cpp(14): error C2227: '->Set' : 左側がクラス、構造体、共用体、ジェネリック型へのポインターではありません。
2>exe_and_dll_test.cpp(18): error C2027: 認識できない型 'testclidll::Class1' が使われています。
2> e:\mycodes\exe_and_dll_test\debug\testclidll.dll : 'testclidll::Class1' の宣言を確認してください。
2>exe_and_dll_test.cpp(18): error C2227: '->Get' : 左側がクラス、構造体、共用体、ジェネリック型へのポインターではありません。
まず、C/C++ではdllをリンクするときにも必ずヘッダファイルをincludeしなければならないが、.NETではdllが情報を持っている(?)ので、そこへさらにインクルードをかけると定義が二重に行われる(のだと思う)。従って、
#include "stdafx.h" using namespace System; //#include "../testclidll/testclidll.h" //このincludeはしてはいけない int main(array<System::String ^> ^args) { testclidll::Class1^ c = gcnew testclidll::Class1; c->Set(10); Console::WriteLine(System::String::Format("{0}\n",c->Get())); return 0; }
再現手順
1. testclidll.hを、以下のように修正する
// testclidll.h #pragma once using namespace System; namespace testclidll { enum ReturnT{ //型を追加 True,False, }; public ref class Class1 { int c; public: void Set(int d); int Get(); ReturnT isOdd(); //メンバが奇数かどうかを返す関数を追加。戻り値はユーザー定義型のReturnT }; }
2.testclidll.cppを以下のように修正する
// これは メイン DLL ファイルです。 #include "stdafx.h" #include "testclidll.h" namespace testclidll { void Class1::Set(int d){ c = d; } int Class1::Get(){ return c; } //メンバが奇数かどうかを返す関数の定義 ReturnT Class1::isOdd(){ if( c % 2 != 0 ) return True; return False; } }
3.exe側を以下のように修正する
#include "stdafx.h" using namespace System; //#include "../testclidll/testclidll.h" int main(array<System::String ^> ^args) { testclidll::Class1^ c = gcnew testclidll::Class1; c->Set(10); testclidll::ReturnT t = c->isOdd();//奇数かどうかの判定を追加 Console::WriteLine(System::String::Format("{0}\n",c->Get())); return 0; }
4.testclidllをコンパイルする
5.exe側をコンパイルする
コンバイルエラー
2>exe_and_dll_test.cpp(16): error C2039: 'ReturnT' : 'testclidll' のメンバーではありません。
2>exe_and_dll_test.cpp(16): error C2065: 'ReturnT' : 定義されていない識別子です。
2>exe_and_dll_test.cpp(16): error C2146: 構文エラー : ';' が、識別子 't' の前に必要です。
2>exe_and_dll_test.cpp(16): error C2065: 't' : 定義されていない識別子です。
これは、クラスの情報は参照したdllの中にあるが、enumのような定数の情報は入っていない(?)ので、定義がないと怒られる。そこで、testclidll.hのincludeを復活させてみる。
#include "stdafx.h" using namespace System; #include "../testclidll/testclidll.h" //やっぱりいるのか? int main(array<System::String ^> ^args) {
1>e:\mycodes\exe_and_dll_test\exe_and_dll_test\../testclidll/testclidll.h(14): error C2011: 'testclidll::Class1' : 'class' 型の再定義
1> e:\mycodes\exe_and_dll_test\debug\testclidll.dll : 'testclidll::Class1' の宣言を確認してください。
1>exe_and_dll_test.cpp(14): error C2027: 認識できない型 'testclidll::Class1' が使われています。
1> e:\mycodes\exe_and_dll_test\debug\testclidll.dll : 'testclidll::Class1' の宣言を確認してください。
1>exe_and_dll_test.cpp(14): error C2227: '->Set' : 左側がクラス、構造体、共用体、ジェネリック型へのポインターではありません。
1>exe_and_dll_test.cpp(16): error C2027: 認識できない型 'testclidll::Class1' が使われています。
1> e:\mycodes\exe_and_dll_test\debug\testclidll.dll : 'testclidll::Class1' の宣言を確認してください。
1>exe_and_dll_test.cpp(16): error C2227: '->isOdd' : 左側がクラス、構造体、共用体、ジェネリック型へのポインターではありません。
1>exe_and_dll_test.cpp(18): error C2027: 認識できない型 'testclidll::Class1' が使われています。
1> e:\mycodes\exe_and_dll_test\debug\testclidll.dll : 'testclidll::Class1' の宣言を確認してください。
1>exe_and_dll_test.cpp(18): error C2227: '->Get' : 左側がクラス、構造体、共用体、ジェネリック型へのポインターではありません。
当然のように最初のエラーが再発する。
enumの定義を別のヘッダファイルへ移動する
testclidllプロジェクト内、defvalheader.hを追加
namespace testclidll { enum ReturnT{ True,False, }; }
それに伴い、dll本体側であるtestclidll.hからenumの定義を消し、includeにする
// testclidll.h #pragma once #include "defvalheader.h" //ReturnTの定義を読み込む using namespace System; namespace testclidll { public ref class Class1 { int c; public: void Set(int d); int Get(); ReturnT isOdd(); }; }
exe側では、testclidll.hではなく、defvalheader.hを読み込む
#include "stdafx.h" using namespace System; #include "../testclidll/defvalheader.h" int main(array<System::String ^> ^args) { testclidll::Class1^ c = gcnew testclidll::Class1; c->Set(10); testclidll::ReturnT t = c->isOdd(); Console::WriteLine(System::String::Format("{0}\n",c->Get())); return 0; }
タスクトレイにアイコンを表示する方法
① アイコンの表示を開始したいタイミングで、Shell_NotifyIcon関数をNIM_ADDで呼び出す
② メッセージループに「タスクトレイのアイコンにアクションがあった」という単位でメッセージが届くので、処理する
③ アイコンを消したいタイミングで、Shell_NotifyIcon関数をNIM_DELETEで呼び出す
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { static const decltype(WM_USER) WM_NOTIFYICON = WM_USER + 100; static const int IconID = 100; switch (msg) { case WM_LBUTTONDOWN://ウィンドウをクリックしたらアイコン表示開始 { NOTIFYICONDATA nid; nid.cbSize = sizeof(nid); nid.hWnd = hwnd; nid.uID = IconID;//複数のアイコンを表示したときの識別ID。なんでもいい。 nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; nid.uCallbackMessage = WM_NOTIFYICON; //WM_USER以降の定数。 nid.hIcon = (HICON)LoadIcon(NULL, IDI_APPLICATION); _tcscpy(nid.szTip, _T("タスクトレイで表示する文字列")); int ret = (int)Shell_NotifyIcon(NIM_ADD, &nid); ShowWindow(hwnd, SW_HIDE); } break; case WM_NOTIFYICON: if (wp == IconID && lp == WM_LBUTTONDOWN) { //アイコンに対する操作がWM_LBUTTONDOWNでアイコンのIDがIconIDなら以下の処理: ShowWindow(hwnd, SW_SHOW); //タスクトレイアイコンを削除 //////////////////////////////////////// NOTIFYICONDATA nid; nid.cbSize = sizeof(nid); nid.hWnd = hwnd; nid.uID = IconID; nid.uFlags = 0; Shell_NotifyIcon(NIM_DELETE, &nid); //////////////////////////////////////// } break; case WM_DESTROY: DestroyWindow(hwnd); PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, msg, wp, lp); }
printfデバッグは便利だが、コンソールを開かなければいけないという欠点がある。
デバッガに出力するならOutputDebugStringという関数があるが、書式指定が使えないので数字の出力には一手間かかる。
_RPT0 , _RPT1 , _RPT2 , _RPT3 , _RPT4
というデバッグ用のマクロがあり、これをつかえばprintfのように書けるのだが、引数の数が決まっていてやや不便。
https://msdn.microsoft.com/ja-jp/library/ms350217(v=vs.71).aspx
_RPTn使用例:
#include <crtdbg.h> void func(int input1,double input2) { _RPT2( _CRT_WARN, "func inputs = %d , %lf \n", input1, input2 ); }
printf(そして可変長引数)は危険なので、デバッグしようとしてバグを増やしてしまっては本末転倒と考えれば、この仕様は合理的だ。しかしそれでもprintf形式が恋しくなることがあるので、自作する。
自作DebugPrintf
void DebugPrintf(LPCTSTR pszFormat, ...) { va_list argp; TCHAR pszBuf[256]; va_start(argp, pszFormat); _vstprintf(pszBuf, pszFormat, argp); va_end(argp); OutputDebugString(pszBuf); }
可変長引数をとる関数を作りそれをそのまま_vstprintfへ流し、OutputDebugStringでデバッガへ出力。ここでは文字数の上限が256となっているが、リスクを承知でやっているのだからこの点は自己責任となる。
C#のサンプルは沢山あるがCLIで実行できずに困ったのでここに書いておく。
下記は、要素数10のdoubleの配列aの内容を、0~9の1.5倍の値で初期化する。
using namespace System; typedef double datatype_t; ref struct mystruct{ array<datatype_t>^ m_a; public: mystruct(array<datatype_t>^ dst){ m_a = dst; } void function(int number){ m_a[ number] = number*1.5; } }; int main(array<System::String ^> ^args) { array<datatype_t>^ a = gcnew array<datatype_t>(10); mystruct^ plfor = gcnew mystruct(a); System::Threading::Tasks::Parallel::For( 0, a->Length, gcnew Action<int>(plfor, &mystruct::function) ); for(int i= 0 ;i < a->Length;i++){ System::Console::WriteLine( System::String::Format("a[{0}] = {1}",i,a[i])); } Console::ReadLine(); return 0; }
ウィンドウプロシージャをメンバ関数にしたい。しかしWNDCLASSEXのlpfnWndProcに指定できるのは普通の関数かstaticメンバ関数のみ。
しかし一工夫することで、ウィンドウをカプセル化することができる。コードは以下。
(ソースコードをまとめたファイル:CnuWindow.zip )
CnuWindow.h
#pragma once #include <windows.h> class CnuWindow { TCHAR WINDOW_CLASS_NAME[1024]; static LRESULT CALLBACK StaticWndProc( HWND hWnd, UINT msg, WPARAM wp, LPARAM lp); private: HWND m_hwnd; HINSTANCE m_hInstance; public: CnuWindow(); virtual ~CnuWindow(); //! @brief ウィンドウ定義用メンバ関数 bool RegistWindow( const TCHAR* window_class_name, const HINSTANCE hInst ); //! @brief ウィンドウプロシージャ virtual LRESULT WndProc( HWND hWnd, UINT msg, WPARAM wp, LPARAM lp ); HWND GetHWnd()const { return m_hwnd; } HINSTANCE GetHInstance()const { return m_hInstance; } //! @brief ウィンドウ作成用メンバ関数 bool nuCreateWindow( LPCTSTR lpWindowName, // ウィンドウ名 DWORD dwStyle, // ウィンドウスタイル int x, // ウィンドウの横方向の位置 int y, // ウィンドウの縦方向の位置 int nWidth, // ウィンドウの幅 int nHeight, // ウィンドウの高さ HWND hWndParent, // 親ウィンドウまたはオーナーウィンドウのハンドル HMENU hMenu // メニューハンドルまたは子ウィンドウ ID ); };
CnuWindow.cpp
#include "CnuWindow.h" #include <windows.h> #include <tchar.h> CnuWindow::CnuWindow() { } CnuWindow::~CnuWindow() { } // ウィンドウを作成する bool CnuWindow::RegistWindow(const TCHAR* window_class_name, const HINSTANCE hInst) { _tcscpy_s(WINDOW_CLASS_NAME, window_class_name); m_hInstance = hInst; WNDCLASSEX wc; // ウィンドウクラスの情報を設定 wc.cbSize = sizeof(wc); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = CnuWindow::StaticWndProc; // ウィンドウプロシージャ wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; // インスタンスハンドル wc.hIcon = (HICON)LoadIcon(NULL, IDI_APPLICATION); wc.hIconSm = wc.hIcon; wc.hCursor = (HCURSOR)LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = NULL; wc.lpszClassName = WINDOW_CLASS_NAME; // ウィンドウクラス名 // ウィンドウクラスを登録する if (RegisterClassEx(&wc) == 0) { return false; } return true; } bool CnuWindow::nuCreateWindow( LPCTSTR lpWindowTitle, // ウィンドウ名 DWORD dwStyle, // ウィンドウスタイル int x, // ウィンドウの横方向の位置 int y, // ウィンドウの縦方向の位置 int nWidth, // ウィンドウの幅 int nHeight, // ウィンドウの高さ HWND hWndParent, // 親ウィンドウまたはオーナーウィンドウのハンドル HMENU hMenu // メニューハンドルまたは子ウィンドウ ID ) { m_hwnd = CreateWindow( WINDOW_CLASS_NAME, // ウィンドウクラス名 lpWindowTitle, // タイトルバーに表示する文字列 dwStyle, // ウィンドウの種類 x, // ウィンドウを表示する位置(X座標) y, // ウィンドウを表示する位置(Y座標) nWidth, // ウィンドウの幅 nHeight, // ウィンドウの高さ hWndParent, // 親ウィンドウのウィンドウハンドル hMenu, // メニューハンドル m_hInstance, // インスタンスハンドル this // その他の作成データ ); if (m_hwnd == nullptr) return false; return true; } LRESULT CnuWindow::WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hWnd, msg, wp, lp); } LRESULT CALLBACK CnuWindow::StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { CnuWindow* This = (CnuWindow*)GetWindowLongPtr(hWnd, GWLP_USERDATA); if (!This) {//取得できなかった(ウィンドウ生成中)場合 if (message == WM_CREATE) { This = (CnuWindow*)((LPCREATESTRUCT)lParam)->lpCreateParams; if (This) { SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)This); return This->WndProc(hWnd, message, wParam, lParam); } } } else {//取得できた場合(ウィンドウ生成後) return This->WndProc(hWnd, message, wParam, lParam); } return DefWindowProc(hWnd, message, wParam, lParam); }
main.cpp
#include<windows.h> #include <tchar.h> #include "CnuWindow.h" int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow) { CnuWindow wino; wino.RegistWindow(_T("WINO"), hInstance); wino.nuCreateWindow( _T("ウィンドウO"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 300, 300, nullptr, nullptr ); MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int)msg.wParam; }
クラス名はCnuWindowにしているので、それを置換すれば独自のクラス名にできる。
内容的にはCreateWindow時にlpParamに自身のthisを入れる典型的な方法なので詳細は他所へ譲るとして、多少解説をする
LRESULT CALLBACK CnuWindow::StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
CnuWindow* This = (CnuWindow*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
if (!This) {//取得できなかった(ウィンドウ生成中)場合
if (message == WM_CREATE) {
This = (CnuWindow*)((LPCREATESTRUCT)lParam)->lpCreateParams;
if (This) {
SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)This);
return This->WndProc(hWnd, message, wParam, lParam);
}
}
}
else {//取得できた場合(ウィンドウ生成後)
return This->WndProc(hWnd, message, wParam, lParam);
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
64ビット環境ではSetWindowLongではなくSetWindowLongPtrを使わなくてはならない。それに伴い、GetWindowLongPtr,GWLP_USERDATAに変更する。
virtual LRESULT WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp);
これをvirtualにする理由は、main.cppでいずれ以下のように書きたいからである。
#include<windows.h> #include <tchar.h> #include <cstdint> #include "CnuWindow.h" class CWin1 : public CnuWindow { LRESULT WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_CREATE: break; case WM_LBUTTONDOWN: MessageBox(hWnd, _T("CWin1"), 0, 0); break; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hWnd, msg, wp, lp); } }; class CWin2 : public CnuWindow { LRESULT WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_CREATE: break; case WM_LBUTTONDOWN: MessageBox(hWnd, _T("CWin2"), 0, 0); break; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hWnd, msg, wp, lp); } }; int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow) { CWin1 win1; win1.RegistWindow(_T("WIN1"), hInstance); win1.nuCreateWindow(_T("ウィンドウ1"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 300, 300, nullptr, nullptr); CWin2 win2; win2.RegistWindow(_T("WIN2"), hInstance); win2.nuCreateWindow(_T("ウィンドウ2"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 400, 300, 300, nullptr, nullptr); CnuWindow wino; wino.RegistWindow(_T("WINO"), hInstance); wino.nuCreateWindow(_T("ウィンドウO"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 800, 300, 300, nullptr, nullptr); MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int)msg.wParam; }
上記コードは、CnuWindowを継承したCWin1,CWin2クラスを作成し、それぞれのインスタンスがまったく異なるウィンドウとして動作する。
WndProcを仮想関数にしないと、CWin1,CWin2を作成してもメッセージが全てCnuWindowの方へ行ってしまうので、変数をグローバルにしなくていい程度のメリットしかなくなってしまう。このようにクラスを作れば、同じコードで違う挙動をするウィンドウを最小限の労力で作ることができる。