「私はド素人、内容には全く自身を持てません」状態からヨチヨチ歩き程度に上達できたと思う今日この頃なので、一旦内容を整理した。

VC++ ver.6 を使用中。

VS 2003 に浮気してみる *

ユーザーキーワードの定義 *

組込み型のintやcharなどは色がついてハイライトされて見やすいが、これをstringやその他自作クラスに適用したいと思うことがある。

そんな場合は、usertype.datというテキストファイルを作り、それの各行にハイライトさせたいキーワードを記述し、devenv.exeと同じディレクトリに保存すれば良い。私の環境では以下だった。

C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\IDE\usertype.dat

MFCとWM_INITDIALOG *

.net 2003 で、例えばダイアログのイベントハンドラを追加する場合は、プロパティから「メッセージ」のアイコンを選んでゴニョゴニョすれば良いが、ここにWM_INITDIALOGが無かったので腹が立った。

原因は、これはオーバーライドするハンドラだからなのであった。これを表示するにはクラスビューを表示させた状態(フロートにすると良い)でプロパティを選ぶ。すると直方体の「オーバーライド」ボタンが登場するので、ここからOnInitDialogを選択してゴニョゴニョすると良い。このあたりはプロパティウィンドウをフロートにしてF1でヘルプを呼ぶと書いてある。でも、ここまで気づくのは相当難しいのではないのか。

MFCのアプリケーションウィザードのActiveXコントロールのチェックボックスの役目 *

ヘルプからの抜粋:

「このオプションを選択せず、後で ActiveX コントロールをプロジェクトに追加する必要が生じた場合には、アプリケーションのメンバ関数 InitInstance に AfxEnableControlContainer の呼び出しを追加する必要があります。 」

Win32API *

いわゆるSDKプログラミング。これを知らねばはじまらない

link *

メモ *

SendMessage(hEdit,EM_SETSEL,0,-1); //全選択
SendMessage(hEdit,EM_SETSEL,-1,-1); //解除

でうまくいった。エディットコントロールについて。

CreateProcess *

コンボボックスとリスト表示 *

コンボボックスで、右の下矢印をクリックすると「びろーん」とドロップダウンリストが出てくる。項目が多いとこれはスクロールウィンドウになるわけだが、その位置をどう指定すればよいか?

そのためにはCB_SETTOPINDEXを投げればよい。ただしこれは、ドロップダウンリストが現れている時にのみ効果があるようだ。マウスクリックでリストを出す場合、ON_CBN_DROPDOWNが飛んではくるが、これはリストが現れる前の話なのでこのタイミングで作業しても意味がないようである。

結局、CB_SETTOPINDEXはいつ投げればいいのだろうか?

サブクラス化 *

コントロールのウィンドウプロシージャとして、自前の関数を登録することができる。

WNDPROC PrevWndProc; 

LRESULT CALLBACK MyWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  if(uMsg == WM_LBUTTONDBLCLK){
    MessageBox(GetParent(hWnd), TEXT("double-click hello"), TEXT(""), MB_OK);
    return 0;
  }
  return CallWindowProc(PrevWndProc, hWnd, uMsg, wParam, lParam);
}

void some_initialization_func(){
  // ...
  HWND hwndTargetCtrl = GetDlgItem(hWndDlg, IDC_TARGET_CONTROL);
  PrevWndProc = (WNDPROC)GetWindowLong(hwndTargetCtrl, GWL_WNDPROC);
  SetWindowLong(hwndTargetCtrl, GWL_WNDPROC, (LONG)MyWndProc);
  // ...
}

などとする。上の例では、コントロールにおけるダブルクリックはメッセージボックスに置き換わる。CallWindowProc してやればデフォルトの処理が実行されるので、記述するのは自分の欲しい機能だけでOK。

フルパスからファイル名を取得する。 *

_splitpath() あるいは http://www3.ocn.ne.jp/~liquidz/tips/tips_cpp000.htm

フォルダ選択ダイアログ *

全て http://homepage2.nifty.com/DSS/VCPP/API/SHBrowseForFolder.htm にて学んだこと。

#include <shlobj.h>
static bool ChooseDirectory(LPSTR NewDir, HWND hwndOwner = NULL){

    // see http://homepage2.nifty.com/DSS/VCPP/API/SHBrowserForFolder.htm
    BROWSEINFO bi;
    LPSTR lpBuffer;
    LPITEMIDLIST pidlBrowse;
    LPMALLOC lpMalloc = NULL;

    HRESULT hr = SHGetMalloc(&lpMalloc);
    if(FAILED(hr)) return false;

    if((lpBuffer = (LPSTR)lpMalloc->Alloc(_MAX_PATH)) == NULL) return false;
    bi.hwndOwner = hwndOwner;
    bi.pidlRoot = 0;    // desktop is the root
    bi.pszDisplayName = lpBuffer;
    bi.lpszTitle = TEXT("Choose your favourite directory...");
    bi.ulFlags = 0x0040; //BIF_NEWDIALOGSTYLE
    bi.lpfn = 0;
    bi.lParam = 0;
    pidlBrowse = SHBrowseForFolder(&bi);
    bool copied = false;
    if(pidlBrowse != NULL){    // not canceled
        if(SHGetPathFromIDList(pidlBrowse, lpBuffer)){
            lstrcpy(NewDir, lpBuffer);
            copied =  true;
        }
        lpMalloc->Free(pidlBrowse);
    }
    lpMalloc->Free(lpBuffer);
    lpMalloc->Release();
    return copied;
}

ダイアログとWM_KEYDOWN *

標準では、ダイアログプロシージャでWM_KEYDOWNは拾えないようだ。

スレッド *

わけのわからないバグは大抵スレッドに絡んでいるが、この前遭遇したわかりにくいバグ(私にとっては。多分初歩のミス)もやっぱりスレッドまわりだったのでメモ。

::WaitForSingleObject という関数があるが、これはカーネルオブジェクトでキッチリ同期をとってくれるスグレモノであるが、キッチリwaitしてくれたおかげでメッセージループも回らなくなり、これが原因でよくわからないバグになっていた。メッセージループ(ウィンドウ)を持つスレッドの場合は、代わりに::MsgWaitForMultipleObjectsExを用いて、カーネルオブジェクトを待ちつつメッセージを処理するべきである。よく見たらそのようにMSDNにも書いてあった。

ここで、CoInitializeなんかは間接的にウィンドウを作るので、明示的にウィンドウを作らないがCoInitializeを呼ぶスレッドなんかも、同様の理由で::WaitForSingleObjectを使うべきではないようだ。

メッセージクラッカー *

ウィンドウプロシージャは大抵飛んでくるメッセージをさばくのにswitch文を使う。処理すべきメッセージなんてのは大抵沢山あるので、巨大なswitch文になりがちだが、これがまた美しくない。そこで、メッセージクラッカーと呼ばれているwindowsx.hで定義されている一連のマクロを使うと比較的すっきりとメッセージ処理が記述できる。直接APIを叩いてアプリケーションを作る場合にとても重宝する。

・・・と、Jeffrey RichterのAdvanced Windowsに書いてあった。試してみたが、ダイアログプロシージャを以下の様にすっきりと記述できて満足。

BOOL CALLBACK DialogProc(HWND hwnd, UINT uMsg, WPARAM wp, LPARAM lp){
	switch(uMsg){
		hiHANDLE_DLGMSG(hwnd,	WM_INITDIALOG,	Dlg_OnInitDialog);
		hiHANDLE_DLGMSG(hwnd,	WM_CLOSE,		Dlg_OnClose);
		hiHANDLE_DLGMSG(hwnd,	WM_COMMAND,		Dlg_OnCommand);
		hiHANDLE_DLGMSG(hwnd,	WM_COPYDATA,	Dlg_OnCopyData);
	}
	return FALSE;
}

正しくは(ダイアログプロシージャの場合)、以下のようなマクロを前もって定義しておく必要がある。

/////////////////////////////////////////////////////////////////////
// The macro (message cracker) is almost same as that of Jeffrey Richter's "CmnHdr.h".
// Just the names of the variables are different from the original ones.
#define hiHANDLE_DLGMSG(hwnd, message, fn)			\
	case(message): return (SetDlgMsgResult(hwnd, uMsg,	\
		HANDLE_##message((hwnd),(wp),(lp),(fn))))
/////////////////////////////////////////////////////////////////////

Dlg_OnInitDialogなどとあるのがハンドラの関数名である。これらのプロトタイプはwindowsx.hに与えられている。でもWM_COPYDATAのが書いてない。これは以下のようになる。

BOOL Dlg_OncopyData(HWND hwnd, HWND hwndFrom, PCOPYDATASTRUCT pcds)

ただし、SetDlgMsgResultはあんまり賢くないみたいで、キャスト関連でC4244の警告が出る。

メッセージクラッカー・ビューアなるものを作ってらっしゃる方もおられた。

ファイルの列挙 *

FindFirstFileとFindNextFileを駆使する。例えばファイル数が知りたければ以下のようにする。ファイル名はWIN32_FIND_DATAのcFileNameでわかる。

int count_files(const TCHAR* mask){
  WIN32_FIND_DATA w32fd;
  HANDLE hfind = FindFirstFile(mask, &w32fd);
  int i = 0;
  if (hfind != INVALID_HANDLE_VALUE) {
    do {
      i++;
    } while(FindNextFile(hfind, &w32fd));
  }
  return i;
}

クリップボード *

DoEvents *

VB6でいうところのDoEventsは以下(なのだそうだ。出典は失念)

MSG msg;
if(::PeekMessage(&msg,NULL,0,0,PM_REMOVE)){
    ::TranslateMessage(&msg);
    ::DispatchMessage(&msg);
}

DLLを使う *

インポートライブラリを使ってビルド時に関数を参照する方法と、実行中に動的に関数を参照する方法とがある。

以前A社の計測装置を購入した際、USBによる操作用にDLLが添付されてきたことがあった(実はそれがお目当て)。しかし、言語のサポートとしてはVisual Basic用の標準モジュールのみで、インポートライブラリはもちろんCのヘッダもなかった。その時標準モジュールを参考にヘッダを書き、何とか動的呼び出しにまでこじつけたのが勉強したきっかけ。

お目当ての関数ポインタには、ちゃんと自ら型(引数、戻り値)を指定してやる。

DLLと .def ファイルがあればインポートライブラリが作れるみたいだ!? 中身は全く理解していないが

lib /def:my_def_file.def

とすれば .libができるみたい。実際にできた。The Code Project - CppSQLite - C++ Wrapper for SQLite - Databaseを参考にした。

IDEの操作 *

ジワジワと操作を覚え、ジワジワと便利になってゆくもの

メモ *

ライブラリ *

ある関数に必要なヘッダとライブラリはヘルプに記載されている。ヘッダは include すれば良い。ライブラリは

のいずれかで追加する(両者はどう違うのだ?)

だけどインクルードパスの追加はできないみたい。

コマンドラインでコンパイル *

プロジェクトを作るまでもない単品なんかはチャチャッとコマンドラインからコンパイラを使いたいと思うことがある。コンパイラは

cl.exe (filename)

で使える。

全般的なこと *

link *

メモ *

同種のコントロールたちを、例えばVBのコントロール配列の様にして一括して処理したい *

以下VC++超初心者のホームページから。

自分で適当に配列を作って、コントロールIDを格納するのが(実装と理解の観点で)一番お手軽

グラフ *

SDKに限った話ではないが、VCでグラフを実現するのはそう簡単なことではなさそうだ。自分で描画することはできるが面倒くさいのでパス。そこで既存のものを使いたくなるわけだが、標準でついてくるのはMSChartくらい。VBから使うのは簡単だが、VCから使うにはかなりのクセモノである上に文書も少ない。定番がどれなのかもよくわからない。CodeGuruでも乱立してるみたいだ。

インストーラ *

National InstrumentsMeasurement Studioを使って、計測制御用のプログラムが使い慣れた言語でラクラク書けてとても幸せだが、成果物の配布にあたっては「インストーラを作りなさい」とのことであった。そういえばインストーラについては全く知らなかったのでWeb上の文書をひととおり調べた結果が以下。

Spy++とWinspector *

VC++付属のSpy++は色々調べるのに便利だが、フリーソフトのWinspectorはその機能を凌駕する。

WTL *

非常に大雑把にいうと「手動MFC亜種」。実行ファイルが軽いという利点がある。MFC、ClassWizardの動作を見つめなおすきっかけにもなる。

link *

メモ *

MFC *

業界標準のフレームワークだが、全くのWindowsプログラミング初学者が手を染めるべきではないと思う。(とりあえずは動作しても、何が何だかわからない状態になると思われる)

link *

メモ *

CEditView *

変更を保存しますか? *

CEditViewを使ったら、アプリケーション終了時に「変更を保存しますか?」とか聞かれるようになってうっとうしい。でも終了時(CMainFrame::OnClose())にビューから

GetDocument()->SetModifiedFlag(FALSE);

したら聞かれなくなった。

一行のみの表示 *

一行のみのCEditViewを作りたいことがあった。でもデフォルトでは複数行表示になっており、スクロールバーもあるので邪魔。PreCreateWindowから cs.style &= ~ES_MULTILINE とかやってみたけどなかなか意図したとおりにならない。で、

cs.style = AFX_WS_DEFAULT_VIEW | ES_NOHIDESEL;

としてみたらなぜかうまくいった。

リストビュー *

一行全体を選択するようにし、グリッドも引く。

m_list.SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);

ある行を選択状態にする。「常に選択を表示」とするのを忘れないようにする。

m_list.SetItemState( next_index, LVIS_SELECTED | LVIS_FOCUSED , LVIS_SELECTED | LVIS_FOCUSED);

消してもよいファイル *

*.aps;*.bsc;*.dll;*.exe;*.exp;*.idb;*.ilk;*.lib;*.ncb;*.obj;
*.ocx;*.pch;*.pdb;*.plg;*.res;*.sbr;*.tlb;*.tlh;*.tli;*.trg

は消してもよいのだそうだ。http://www.mtakahashi.com/old/a2725.html での A2705「もろ」氏。まあ、「ビルドの際に自動で生成されるから」という理由だから、よそから引っ張ってきたdll、exe、lib、tlh、tliなどなどは当然消したらダメ。

SDIウィンドウの作成 *

最初にAppWizardさんが作ってくれるが、後から自分で作りたい場合はどうすればよいか?

に記述が見つかった。指定するリソースIDのメニューを前もって作っておかねばうまくいかなかった。

CEditView *

CHtmlView *

COM絡み *

フォント *

フォントまわりは奥が深い。CreateFontの引数には泣きそうになる。よって、とりあえず動いたやつをメモとして貼る。CFontのメンバ関数とグローバルなAPIが同名だが、引数は同じっぽい。

CreateFont(20, 0, 0, 0, FW_NORMAL, FALSE, FALSE, 0, SHIFTJIS_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, _T("MS ゴシック"));

CFontなら以下のようなお手軽設定も可能。

hFont.CreatePointFont(15*10, _T("MS ゴシック"));

設定させるにはWM_SETFONTのwParamにHFONTをのせてやってメッセージを送る

HFONT hFont;
...
::SendMessage(hWnd, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE,0));

か、CWndのSetFontメンバを使って

SetFont(&hFont,TRUE); // CFont hFont;

とする。しかしCFontオブジェクトがローカル変数だとこれは失敗するので、CWndのメンバにしておくとよし。(ナレッジウェア Tips3 - SetFont)

リストビュー *

使用する際には、stdafx.hに Afxcview.h をインクルードしておくこと。リストビュー(CListView)は正体がリストコントロール(CListCtrl)なビュークラスだそうだ。ダイアログ等にリストコントロールを直接貼り付ける時と違い、スタイルはリソースから指定できない。「レポート」にするにはPreCreateWindow内で

cs.style &= ~LVS_TYPEMASK;
cs.style |= LVS_REPORT;

とする。その他SetExtendedStyleまわりはOnInitialUpdateに記述するとよし。(以上はClistViewの表示がうまくいかない・・・。における記述を引用)

オーナードロー、カスタムドロー *

ダイアログベースでEnter *

「MFCでEnterキーが反応してしまう」のやりとりを参考にしました。

ダイアログのデフォルト動作では、Enterによって終了してしまう。これを変えるには例えば以下のようにする。

BOOL CxxxDlg::PreTranslateMessage(MSG* pMsg)
{
    if(pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_RETURN)
        return TRUE;
    return CDialog::PreTranslateMessage(pMsg);
}

ユーザー定義メッセージ *

MFCではメッセージマップをClassWizardで楽々記述できるのが大変らくちんなのだが、ユーザー定義メッセージを扱う際には手動でなくてはいけない。

エディットボックスの表示更新のタイミングについて *

調べようと思ってとりあえず以下の文書を見つけたが、詳しい検証はしていない。

スレッド *

's'の初期化が'default'ラベルによって行われませんでした *

switch文を使っていたら上記のようなエラー(C2361)に遭遇した。あるcaseのみで使う変数をそこで宣言した場合に、残りのcaseでもスコープが残っているけどいいのか、というニュアンスなのだそうだ。こんな時は変数を宣言・使用している箇所をブロックの中に閉じ込めてしまえばよい。

・・・という内容を http://frog.raindrop.jp/knowledge/archives/000223.html にて学びました。

右クリックメニュー *

右クリックでポップアップメニューを出す方法を見て学んだ。このページに記述されていることではあるが、念のためこちらにも書く。何か処理をする(ハンドラを書く)ためにはメニューIDとハンドラとの対応をとってやらないといけない。このあたりはClassWizardにやらせるとラクだが、メニューIDを直接ソースに書くとClassWizardはそれに気づいてくれない。そこで、(実際にはどこにも使われないけど)メニューリソースをひとつ用意し、そこで適当な メニューIDを登録しておけばよい。するとClassWizardはそれを認識してくれるので、同じメニューIDを用いれば右クリックメニューのハンドラが楽に実装できる。

SDK雛型 *

シングルウィンドウ *

#include<windows.h>
const char lpClassName[]="SDKTemplate";    // window class

LRESULT CALLBACK WndProc(HWND hwnd , UINT msg , WPARAM wp , LPARAM lp) {
    switch (msg) {
        case WM_COMMAND:
        switch(LOWORD(wp)) {
            //case BUTTON:
            //break;
            }
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        }
        return DefWindowProc(hwnd , msg , wp , lp);
}

int WINAPI WinMain(HINSTANCE hInst , HINSTANCE hPrevInstance , PSTR lpCmdLine , int nCmdShow ) {
    HWND hwnd;
    MSG msg;
    WNDCLASS winc;

    winc.style	= CS_HREDRAW | CS_VREDRAW;
    winc.lpfnWndProc = WndProc;
    winc.cbClsExtra = 0;
    winc.cbWndExtra = 0;
    winc.hInstance = hInst;
    winc.hIcon	= LoadIcon(NULL , IDI_APPLICATION);
    winc.hCursor = LoadCursor(NULL , IDC_ARROW);
    winc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    winc.lpszMenuName = NULL;
    winc.lpszClassName = lpClassName;

    if (!RegisterClass(&winc)) return 0;

    hwnd = CreateWindow(
        lpClassName ,    // class name
        TEXT("My SDK") , // window name
        WS_OVERLAPPEDWINDOW | WS_VISIBLE ,
        100 , 100 , // horizontal, vertical position
        200 , 200 , // width and height
        NULL ,	// parent window
        NULL ,	// menu
        hInst , // application instance
        NULL // window-creation data
    );

    if (hwnd == NULL) return 0;

    while (GetMessage(&msg , NULL , 0 , 0)) DispatchMessage(&msg);
    return msg.wParam;
}

ダイアログベース *

以上を参考に作成しました。

#include<windows.h>
#include"resource.h"

INT_PTR CALLBACK DialogProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp)
{
  switch(msg){
  case WM_INITDIALOG:
    // initialize
    return TRUE;
  case WM_COMMAND:
    switch(LOWORD(wp)){
    case IDOK:
      EndDialog(hDlg,LOWORD(wp));
      return TRUE;
    case IDCANCEL:
      EndDialog(hDlg,LOWORD(wp));
      return TRUE;
    }
    break;
  case WM_CLOSE:
    PostQuitMessage(0);
    break;
  }
  return FALSE;
}


int WINAPI WinMain(HINSTANCE hinst, HINSTANCE, PTSTR pCmdLine, int nCmdShow)
{
  DialogBox(hinst, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DialogProc);
  return 0;
}

こちらはメッセージクラッカーを使用するもの。

#include<windows.h>
#include <windowsx.h>
#include"resource.h"


/////////////////////////////////////////////////////////////////////
// The macro (message cracker) is almost same as that of Jeffrey Richter's "CmnHdr.h".
// Just the names of the variables are different from the original ones.
#define hiHANDLE_DLGMSG(hwnd, message, fn)      \
  case(message): return (SetDlgMsgResult(hwnd, uMsg,  \
    HANDLE_##message((hwnd),(wp),(lp),(fn))))
/////////////////////////////////////////////////////////////////////


void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify){
  switch(id){
    case IDCANCEL:
      EndDialog(hwnd, id);
      break;
    case IDOK:
      // do something
      break;
  }
}

BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lp){
  // initialization code
  return TRUE;
}

void Dlg_OnClose(HWND hwnd){
  EndDialog(hwnd, 0);
}


INT_PTR CALLBACK DialogProc(HWND hwnd, UINT uMsg, WPARAM wp, LPARAM lp){
  switch(uMsg){
    hiHANDLE_DLGMSG(hwnd,  WM_INITDIALOG,  Dlg_OnInitDialog);
    hiHANDLE_DLGMSG(hwnd,  WM_CLOSE,    Dlg_OnClose);
    hiHANDLE_DLGMSG(hwnd,  WM_COMMAND,    Dlg_OnCommand);
  }
  return FALSE;
}

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE, PTSTR pCmdLine, int nCmdShow)
{
  DialogBox(hinst, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DialogProc);
  return 0;
}

組込みスクリプトエンジン *

調べ中。

WindowsのScripting Hostも魅力的な機能


Last-modified: Sun, 01 Oct 2006 13:58:16 JST