13.メニュー項目やコントロールを無効化する

・メニュー項目を無効化する。

EnableMenuItem( HMENU hMenu, UINT uIDEnableItem, UINT uEnable );

    hMenu        :メニューのハンドル
    uIDEnableItem:メニュー項目の ID 又は番号
    uEnable      :有効化:MF_ENABLED
                  無効化:MF_DISABLED
                  淡色表示で無効化:MF_GRAYED

    例1: EnableMenuItem(hMenu, id, MF_BYCOMMAND | MF_GRAYED);
         id: コマンドID

    例2: EnableMenuItem(hMenu, id, MF_BYPOSITION | MF_GRAYED);
         id: 0番目から始まる番号
         メニュー項目がポップアップでコマンドIDが無い場合でも使用できる。

    例3: ファイル(F)→新規作成(N)を無効化の場合
         case WM_MENUSELECT:
           UINT index;
           index = (UINT) LOWORD(wParam);
           if(index == 0){
             HMENU hMenuSub = GetSubMenu(g_hMenu, index);
             EnableMenuItem(hMenuSub, 0, MF_BYPOSITION | MF_GRAYED);
           }
           break;


・コントロールなどを無効化する。

EnableWindow( HWND hWnd, BOOL bEnable );

    hWnd   :コントロールのハンドル
    bEnable:有効化:TRUE
            無効化:FALSE


・ツールバーボタンを無効化する。

SendMessage(hTool, TB_ENABLEBUTTON, ID, MAKELONG(FALSE, 0));
・非表示
SendMessage(hTool, TB_HIDEBUTTON, ID, MAKELONG(TRUE, 0));

14.メニュー項目やツールバーボタンにチェックを入れる

メニューの場合

ID:メニューアイテムのコマンドID
bool Menu_State_Check(UINT ID)
{
    MENUITEMINFO    mi;
    
    ZeroMemory (&mi, sizeof(mi));
    mi.cbSize = sizeof(mi);
    mi.fMask = MIIM_STATE;
    
    GetMenuItemInfo(g_hMenu,ID,0,&mi);
    
    if(mi.fState == MFS_UNCHECKED)
        mi.fState = MFS_CHECKED;
    else
        mi.fState = MFS_UNCHECKED;
    
    SetMenuItemInfo(g_hMenu,ID,0,&mi);
    
    if(mi.fState == MFS_UNCHECKED)
        return 0;
    return 1;
}// Menu_State_Check

メニュー(択一形式)
0 〜 5番目までのアイテムのうち2番目にチェックを入れる
CheckMenuRadioItem(hMenu, 0, 5, 2, MF_BYPOSITION | MFS_CHECKED);
MF_BYCOMMAND を使用する場合は、リソースヘッダのコマンドIDの順番を
正しく並び替える必要がある

ツールバーボタンの場合

// チェック
SendMessage(hTool, TB_CHECKBUTTON, ID, MAKELONG(TRUE, 0));
// 未チェック
SendMessage(hTool, TB_CHECKBUTTON, ID, MAKELONG(FALSE, 0));


または、TB_SETSTATEを使用する
TB_SETSTATEのみを使うと次のボタンが消えるので、TB_GETSTATEと併用する

// チェック
int flag = (int)SendMessage(hTool, TB_GETSTATE, ID, 0);
SendMessage(hTool, TB_SETSTATE, ID, MAKELONG(flag | TBSTATE_CHECKED, 0));
// 未チェック
int flag = (int)SendMessage(hTool, TB_GETSTATE, ID, 0);
SendMessage(hTool, TB_SETSTATE, ID, MAKELONG(flag & ~TBSTATE_CHECKED, 0));


////////// ボタンの種類 //////////

// ノーマルボタン
TBBUTTON	tbb;
ZeroMemory(&tbb,sizeof(TBBUTTON));
tbb.fsStyle = TBSTYLE_AUTOSIZE;
tbb.fsState = TBSTATE_ENABLED;

// 択一ボタン
tbb.fsStyle = TBSTYLE_AUTOSIZE | TBSTYLE_CHECKGROUP;
tbb.fsState = TBSTATE_ENABLED;

// トグルボタン
tbb.fsStyle = TBSTYLE_AUTOSIZE | TBSTYLE_CHECK;
tbb.fsState = TBSTATE_ENABLED;

// セパレータ
tbb.fsStyle = TBSTYLE_SEP;
tbb.fsState = 0;

15.多数のコントロールを作成する

 コントロールのIDを省略すると、OS全体のフォント表示異常などの誤作動を起こす。
 たくさんのコントロールを作成するときは、コントロールやコマンドのIDを
 1つ1つ#defineしたのでは大変なので数値のまま使用した方が良いかもしれない。
 resource.hと重ならないように確認する。
 1つのコントロールに複数のIDが付くと、メモリリークのようになって
 異常終了するので注意する。
 コントロールのIDに、WM_USER は使わない。
 
 以下は、ツールボタンとピクチャの作成例である。
 comctl32.lib のリンク設定が必要。
 今回は、
   ToolBar ID: 1500
   Pic     ID: 1501 〜
   Command ID: 1564 〜
 とした。
#include <windows.h>
#include <commctrl.h>
#include "resource.h"

#define IDM_OPEN 10000

HINSTANCE   g_hInst = NULL;
HWND        g_hTool = NULL;
HWND        g_hPic[24] = {NULL};
HIMAGELIST  g_hIml = NULL;

// num1 :始まりの数  num2 :終わりの数
// p_x  :X座標       p_y  :Y座標
// p_wdh:横幅        p_hgt:高さ
void Set_Pic(HWND *hPic, HWND hParent, int num1, int num2,
             int x, int y, int wdh, int hgt)
{
    static int ct = 0;
    for(int i=num1;i<num2;i++){
        hPic[i] = CreateWindowEx(WS_EX_CLIENTEDGE,
                "STATIC",
                "",
                WS_CHILD | WS_VISIBLE | SS_NOTIFY | SS_BITMAP,
                (wdh + 2) * (i - num1) + x, y, wdh, hgt,
                hParent,
                (HMENU) 1501 + ct++,
                g_hInst,
                NULL);
    }
}// void Set_Pic(HWND *hPic, HWND hParent, RECT rc)

HWND Create_ToolBar(HWND hParent)
{
    HWND hTool = CreateWindowEx( 0,
                                 TOOLBARCLASSNAME,
                                 NULL,
                                 WS_CHILD | WS_VISIBLE,
                                 0, 0, 0, 0,
                                 hParent,
                                 (HMENU) 1500,
                                 g_hInst,
                                 NULL);
    SendMessage(hTool,TB_BUTTONSTRUCTSIZE,sizeof(TBBUTTON),0);
    return (hTool);
}

void Insert_Bttn(int num)
{
    TBBUTTON tbb;
    ZeroMemory(&tbb, sizeof(TBBUTTON));
    tbb.fsStyle   = TBSTYLE_AUTOSIZE | TBSTYLE_CHECKGROUP;
    tbb.fsState   = TBSTATE_ENABLED;
    tbb.iString   = -1;
    tbb.iBitmap   = num;
    tbb.idCommand = 1564 + num;
    SendMessage(g_hTool, TB_INSERTBUTTON, num, (LPARAM) &tbb);
}

void Set_ToolBar(HWND hParent){
    // ツールバーコントロールの作成
    g_hTool = Create_ToolBar(hParent);
    
    int bmp_wdh = 32;    // 画像の横幅
    int bmp_hgt = 30;    // 画像の高さ
    int bmp_n   = 16;    // 画像の数

    // イメージリストの作成
    g_hIml  = ImageList_Create(bmp_wdh, bmp_hgt, ILC_COLORDDB, 0, 0);
    SendMessage(g_hTool, TB_SETIMAGELIST, 0, (LPARAM) g_hIml);
    
    // ツールバーにボタンと画像を追加する
    int i;
    for(i=0;i<bmp_n;i++){
        HBITMAP hBmp;
        hBmp = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP1));
        ImageList_Add(g_hIml, (HBITMAP) hBmp, NULL);
        DeleteObject(hBmp);
        Insert_Bttn(i);
    }
}

LRESULT CALLBACK WndProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_CREATE:
    {
        InitCommonControls();
        // ツールバーを追加
        Set_ToolBar(hWnd);
        // ピクチャを追加
        Set_Pic(g_hPic, hWnd,  0,  8, 3, 45+19*0, 17, 17);
        Set_Pic(g_hPic, hWnd,  8, 16, 3, 45+19*1, 17, 17);
        Set_Pic(g_hPic, hWnd, 16, 24, 3, 45+19*2, 17, 17);
    }
        break;
    case WM_SIZE:
        SendMessage(g_hTool, TB_AUTOSIZE, 0, 0);
        break;
    case WM_COMMAND:
        switch( LOWORD(wParam) )
        {
        case IDM_OPEN:
            break;
        default:
            if(LOWORD(wParam) >= 1564 &&
               LOWORD(wParam) < 1564 + 16)
            {
                // ここでボタンのコマンドを処理する
            }
            int i;
            switch (HIWORD(wParam))
            {
            case STN_DBLCLK:
                for(i=0;i<32;i++){
                    if((HWND) LOWORD(lParam) == g_hPic[i]){
                        // ここでダブルクリックの処理をする
                        break;
                    }
                }
                break;
            case STN_CLICKED:
                for(i=0;i<32;i++){
                    if((HWND) LOWORD(lParam) == g_hPic[i]){
                        // ここでクリックの処理をする
                        break;
                    }
                }
                break;
            }
        }
        break;
    case WM_CLOSE:
        ImageList_Destroy(g_hIml);
        DestroyWindow(hWnd);
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, uMsg, wParam, lParam);
    }
    return 0;
}

16.INIファイル

プログラムの設定をファイルに保存して、次に起動したときにそのパラメータを使う。
他には、レジストリがある。
// abc.ini
// [ABC]
// DIR=c:\abc\images の場合

// 設定を保存する
char lpDir_w[] = "c:\\abc\\images";
WritePrivateProfileString("ABC","DIR",lpDir_w,".\\abc.ini");

// 設定を読み込む
char lpCurDir[MAX_PATH],lpDir_r[MAX_PATH];
GetCurrentDirectory(MAX_PATH,lpCurDir); // 現在のディレクトリを取得
GetPrivateProfileString("ABC","DIR",lpCurDir,lpDir_r,sizeof(lpDir_r),".\\abc.ini");

17.リソースの列挙とポインタの取得

リソースID を列挙して配列に格納するサンプルと
リソースから文字列を読み込んで表示するサンプル。
wave, jpegなども同じ。
リソースタイプの列挙は、EnumResourceTypes 関数を使用する。

ビットマップ、アイコン、カーソルの場合は、
LoadBitmap(), LoadIcon(), LoadCursor(), LoadImage() を使用。

文字列、キーボードアクセラレータの各テーブルは、
LoadString(), LoadAccelerators()を使用。

/////////////////////////      列挙      /////////////////////////
#define RES_MAX     256

WORD PicName[RES_MAX];  // 列挙したリソースのIDを格納する配列
int g_num = 0;          // リソースの数

BOOL CALLBACK MyEnumRes(HINSTANCE hModule,LPCTSTR lpszType,LPTSTR lpszName,LONG lParam)
{
    if( HIWORD(lpszName) == 0 ){    // Name is from MAKEINTRESOURCE()
        PicName[g_num ++] = (WORD) lpszName;
        if( g_num >= RES_MAX )
            return FALSE;
    }
    else{                           // Name is string
    }
    return TRUE;
}

main()
{
    EnumResourceNames(NULL,"JPEG",MyEnumRes,NULL); // リソースの列挙
}


///////////////////////// ポインタの取得 /////////////////////////

// 指定されたリソースの情報ブロックハンドルを取得
HRSRC   hFound = FindResource(NULL,MAKEINTRESOURCE(IDR_TEXT1),"text");
// 指定されたリソースをグローバルメモリにロード
HGLOBAL hRes   = LoadResource(NULL,hFound);
// リソースのポインタを取得
LPVOID  pMem   = LockResource(hRes);
// 描画
TextOut(hDC,30,80,(char*)pMem,SizeofResource(NULL,hFound));

18.IMEの入力モード変更

Windows7 では使えない。
imm32.lib のリンク設定が必要。
(あるいは、#pragma comment(lib, "imm32.lib") をソースに追加)

変換モード説明
IME_CMODE_CHARCODE1:文字コード入力0:
IME_CMODE_EUDC1:EUDC変換(外字)0:
IME_CMODE_FIXED1:固定変換0:
IME_CMODE_FULLSHAPE1:全角0:半角
IME_CMODE_HANJACONVERT1:HANJA変換0:
IME_CMODE_KATAKANA1:カタカナ0:ひらがな
IME_CMODE_NATIVE1:NATIVEモード0:アルファベットと数字
IME_CMODE_NOCONVERSION1:無変換0:
IME_CMODE_ROMAN1:ローマ字0:
IME_CMODE_SOFTKBD1:ソフトキーボード入力0:
IME_CMODE_SYMBOL1:SYMBOLモード0:

文章モード説明
IME_SMODE_AUTOMATIC自動モード
IME_SMODE_NONEなし
IME_SMODE_PHRASEPREDICT句変換
IME_SMODE_PLURALCLAUSE節変換
IME_SMODE_SINGLECONVERT単語変換
IME_SMODE_CONVERSATION会話モード。チャットアプリで使い易い
#include < imm.h >

HIMC hImc;
HWND hWndEdit1;

hWndEdit1 = GetDlgItem(hDlg,IDC_EDIT1);

hImc = ImmGetContext(hWndEdit1);// 指定ウィンドウのコンテキストハンドルを取得する

if (ImmSetConversionStatus(hImc,IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE
    | IME_CMODE_ROMAN | IME_CMODE_FIXED, IME_SMODE_AUTOMATIC) == 0)
    MessageBox(NULL,"全角ひらがなモードに変換できません。","IME",MB_OK | MB_ICONSTOP);

if (ImmReleaseContext(hWndEdit1, hImc) == 0)
    MessageBox(NULL,"コンテキストの解放に失敗しました。","IME",MB_OK | MB_ICONSTOP);

19.フォントの列挙

#include <windows.h>
#include <mbstring.h>

char g_fontName[256][LF_FACESIZE]; // フォント名
int  g_font_ct = 0;                // フォント数

int CALLBACK ProcEnumFont(const LOGFONT *lplf, const TEXTMETRIC *lpntme, ULONG FontType, LPARAM lParam)
{
    bool flag = 0;
    for(int i=0;i<g_font_ct;i++){
        if(!strcmp(g_fontName[i], lplf->lfFaceName)){
            flag = 1;
        }
    }
    if(!flag && lplf->lfFaceName[0] != '@'){ // 同名、又は先頭に@が付いたフォント名は入れない
        strcpy(g_fontName[g_font_ct], lplf->lfFaceName);
        g_font_ct ++;
        if(g_font_ct >= 256){
            return 0;
        }
    }
    return 1;
}// EnumFontFamExProc

int CharComp(const unsigned char *a, const unsigned char *b)
{
    return _mbscmp(a, b);
}// CharComp

void GetFontName(HWND hWnd)
{
    HDC     hDC = GetDC(hWnd);
    LOGFONT lFont;
    ZeroMemory(&lFont, sizeof(LOGFONT));
    lFont.lfCharSet = DEFAULT_CHARSET;
    strcpy(lFont.lfFaceName, "");
    lFont.lfPitchAndFamily = 0;
    g_font_ct = 0;
    EnumFontFamiliesEx(hDC, &lFont, &ProcEnumFont, NULL, 0);
    qsort(g_fontName, g_font_ct, sizeof(char)*LF_FACESIZE, (int (*)(const void*, const void*))CharComp);
    ReleaseDC(hWnd, hDC);
}// GetFontName

20.シフト演算の変数型

 シフト演算専用の型は存在しないが、BYTE、WORD、DWORD などが
比較的使われやすいようである
WORD	b = 8,c;    // WORDは、符号なし2バイト整数型
bool	a = 1;      // boolは、1バイト整数型。ただし、取り得る値は 0 又は 1 のみ。

c = a << b;

としても、

WORD	a = 1,b = 8,c;

c = a << b;

としても、結果は同じ c == 0x100 となる。

シフトの結果が、aの型の上限値を超えても問題はない。
(cの型が__int64の場合は例外で、aも__int64でなくてはならない)
結果がcの型の上限値を超えた場合は、超えた部分が切り捨てられるだけである。
ただし、bの値がcの型のビット幅以上の場合は、オーバーフローで未定義となる。
上の例では、bが16以上のとき未定義となる。

BOOL は、int           のことで、符号付き 4バイト整数型
LONGLONG は、__int64   のことで、符号付き 8バイト整数型
BYTE は、unsigned char のことで、符号なし 1バイト整数型
WORD は、unsigned shortのことで、符号なし 2バイト整数型
UINT は、unsigned int  のことで、符号なし 4バイト整数型
ULONGは、unsigned long のことで、符号なし 4バイト整数型
DWORDは、unsigned long のことで、符号なし 4バイト整数型

21.変数・配列・ポインタ

 この項目のサンプル コードは、CPPファイルでコンソール アプリとして作成する

 変数は、データを入れる箱です。
 C言語では、箱のサイズが、あらかじめ決まっていないと使えません。
 例えば、
    short i;
 とすると、2バイトのサイズの箱が1つ作られます。
 i には、-32768〜32767 までの整数を1つ入れることが出来ます。
 Win32のint型は、4バイトです。

 配列は複数の変数を1つにまとめたものです。
 変数
    int a,b,c,d,e,f,g,h,i,j;
    a = 2; b = 3; c = 4; ・・・
 は、配列に置き換えると、
  int ary[10];
  ary[0] = 2; ary[1] = 3; ary[2] = 4; ・・・
 となります。

 文字の場合は
    char s[10];
    s[0] = `h`; s[1] = `e`; s[2] = `l`; ・・・
 となりますが、文字が複数あるときは通常 strcpy や memcpy を使います。
    char s[10];
    strcpy(s,"hello!");
    s[0] = 'g';
    printf("%s",s);
 この場合は、gello! と出力されます。
  strcpy は、文字列の最後の'\0'までコピーするので、
  hello! の配列サイズは、6 ではなく 7 以上必要です。
  strncpy や memcpy が '\0' を付けるかどうかは場合によります。

 ポインタは、サイズの決まっていない配列のことです。
 そのため、使用するときには、サイズを決める必要があります。
 malloc(), LocalAlloc(), GlovalAlloc(), new演算子 などの関数を
 使ってポインタにメモリを割り当てます。
 この割り当ては、確保とも言います。
 型や配列に sizeof演算子を使うと型や配列のサイズが得られます。
 ポインタに sizeof演算子を使ってもポインタのサイズは得られません。
 
 変数や配列は、値しか記憶しませんが、ポインタは、アドレスと値の2つのデータを記憶します。
 アドレスとは、メインメモリやハードディスクのメモリ領域に付いている仮想アドレスの事です。
 この仮想アドレスやメモリ領域は、OSや他のソフトも使うため、メモリを確保して使用した後は、
 解放して、他のソフトも使える状態にする必要があります。
 
 例えば、ptという名前のint型ポインタの宣言は、
  int *pt;
 とします。
 この状態では、int型変数分のサイズしか確保されませんが、malloc() などの関数を使って
 メモリ確保したわけじゃないから、解放する必要もありません。
  *pt = 100;
 とすると、ptポインタに 100 という値が保存されます。
  printf("%d", *pt);
 とすると、100 が出力されます。
  printf("%d", pt[0]);
 と書いても結果は同じです。
  printf("%d", pt);
 とすると、pt のアドレスが表示されます。
  printf("%d", &pt[0]);
 と書いても結果は同じです。
 アドレスに、* を付けると値になり、値に、& を付けるとアドレスになるというわけです。
 変数や配列は、アドレスを記憶しないと書きましたが、実は、変数や配列に & を付けても
 アドレスを返します。
 ただし、その場合は、関数の引数に付けて、関数内での値の変更を呼び出し側にも反映させ
 たい場合などに限られます。
 
 前述の配列aryは、ポインタを使うと
    int *pt;
    pt = new int [10];
    *pt = 2; *(pt+1) = 3; *(pt+2) = 4; ・・・
    delete[] pt;
 と置き換えることができます。
 (pt+1)の部分は、アドレスに1を足している事になり、ここでは、ポインタを int型にしたため、
 32ビットOSの場合は、4バイト足したことになります。
 例えば、アドレスが、10000だった場合、10004となるわけです。
 64ビットOSの場合は、int 型のサイズは、8バイトになるため、8足されます。
    *pt = 2; *(pt+1) = 3; *(pt+2) = 4; ・・・
 の部分は、下のように書いても同じです。
    pt[0] = 2; pt[1] = 3; pt[2] = 4; ・・・
 キャストするときは、これよりも上の書き方の方が使いやすいです。
 new演算子で確保したメモリ領域は、ポインタ使用後にdelete演算子で
 解放しなくてはなりません。

 サイズが 1 のポインタは、次のように記述します。
    int *pt = new int;
 この場合は、delete の記述方法を変える必要があります。
    delete pt;
 つまり、ポインタが配列の場合は、delete[] を それ以外は delete を
 使います。

 5個のint型配列を作成するのに、
 malloc関数は、
    int *pt = (int*) malloc(5*sizeof(int));
    free(pt);
 new演算子は、
    int *pt = new int[5];
    delete[] pt;
 とします。
 new演算子で malloc のようにバイト単位で配列サイズを指定して作成したい場合は、
    int *pt = (int*) new BYTE[5*sizeof(int)];
    delete[](LPBYTE)pt;
 とします。

 mallocは、領域確保後に、reallocで領域サイズを変更できます。
    int *pt = (int*) malloc(5*sizeof(int));
    size_t size = _msize(pt);
    realloc(pt, size+10*sizeof(int)); // 更に、10 追加
    free(pt);

 new演算子は、サイズ変更ができないので、場合に応じてnew演算子とmallocを
 使い分ける必要があります。
 new で確保した領域に free は使えません。
 malloc で確保した領域に delete は使えません。

 ソースファイルの拡張子が、.c の場合は、new演算子を使えません。
 mallocは、.c でも .cpp でも使えます。

 関数で使用するとき、引数が変数の場合は戻り値を返さないと
 呼び出し元の関数内で 変更が反映されませんが、
 配列やポインタのアドレスを引数にする場合は戻り値を
 返さなくても反映されます。
 『引数が変数の場合は、変数の値のみが渡され、
 アドレスの場合は、変数の実体が渡される』と考えると良いです。
 この手法は、戻り値が複数必要な場合に使うそうです。
 [変数の場合]
    #include <stdio.h>
    int sub(int i)
    {
      return (i+5);
    }
    
    void main()
    {
      int k;
      k = 3;
      k = sub(k);
      printf("%d",k); // 8が出力される
    }
  [ポインタの場合]
    #include <stdio.h>
    void sub(int *i)
    {
      *i += 5;
    }
    
    void main()
    {
      int k;
      k = 3;
      sub(&k);        // 引数は変数のアドレス
      printf("%d",k); // 8が出力される
    }
 ポインタは、

    int *pt1,*pt2;
    pt1 = new int;
    *pt1 = 2;
    pt2 = pt1;
    delete pt1;
//  delete pt2; ←これを実行すると強制終了
    
 としても、pt1をpt2にコピーしたことにはなりません。
 この場合、pt1とpt2は同じメモリ領域を共有しているので、
 pt1を書き換えるとpt2が、pt2を書き換えるとpt1が同じ値に書き換わります。
 どちらかをdeleteすると、もう一方もdeleteされます。
 実体のないポインタをdeleteするとエラーが発生して終了します。
  
 コピーするときは、memcpy()やmemmove()を使用します。
  どちらかを書き換えても、もう一方はそのままの値です。
  両方とも、領域の確保と解放が必要です。
  
    int *pt1,*pt2;
    pt1 = new int;
    pt2 = new int;
    *pt1 = 2;
    memcpy(pt2,pt1,sizeof(int));
    delete pt1;
    delete pt2;


 ポインタを関数の引数に使用する場合、予めサイズを確保して呼び出す必要があります。
 [○ 良い例1]
  #include <stdio.h>
  #include <string.h>
  void sub1(char *a)
  {
    char s[] = "hello!";
    memcpy(a, s, strlen(s)+1);
  }
  void main()
  {
    char *a;
    a = new char[7];
    sub1(a);
    printf("%s",a);
    delete[] a;
  }
 [× 悪い例]
  #include <stdio.h>
  #include <string.h>
  void sub1(char *a)
  {
    char s[] = "hello!";
    a = new char[7];
    memcpy(a, s, strlen(s)+1);
  }
  void main()
  {
    char *a;
    sub1(a);
    printf("%s",a);
    delete[] a;
  }
 [○ 良い例2]
  #include <stdio.h>
  #include <string.h>
  char *a;
  void sub1()
  {
    char s[] = "hello!";
    a = new char[7];
    memcpy(a, s, strlen(s)+1);
  }
  void main()
  {
    sub1();
    printf("%s",a);
    delete[] a;
  }
 ポインタは基本的に、メモリ確保したスコープで解放を行うようにするが、
 解放しなかった場合、メモリリークになることから分かるように、
 スコープの外に出ても領域と値は保持される。
 それを利用すると次のように他の関数で内部変数のポインタを使用可能である。

 戻り値がポインタの場合

    #include <stdio.h>
    #include <string.h>
    #include <malloc.h>
    char* Get_String()
    {
        char *str = (char*) malloc(256);
        strncpy(str, "abcdefghijklmnopqrstuvwxyz", 256);
        return str;
    }
    void main()
    {
        char *str = Get_String();
        printf(str);
        free(str);
        getchar();
    }


 ポインタは、読み込みたいファイルデータのサイズに合わせて、配列を作成したい
 ときなどに使えます。

    HANDLE hFile;
    DWORD  size;
    DWORD  dwRead;
    char   *buf = NULL;
    hFile = CreateFile("sample.txt",GENERIC_READ,FILE_SHARE_READ,NULL,
                        OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
    if(hFile){
        size = GetFileSize(hFile,NULL);
        buf  = new char[size];
        ReadFile(hFile,buf,size*sizeof(char),&dwRead,NULL);
        CloseHandle(hFile);
    }
    if(buf){
        printf("%s",buf);
        delete[] buf;
        buf = NULL;
    }


 ポインタは、使用前にメモリ領域を確保すると書いたが、既にメモリ領域が確保されている
 ポインタとそれを共有する場合は、必要がない。
 次は、コンソールではなく、ウィンドウ アプリの例である。
 g_Dib.dsBm.bmBits は、ビットマップのピクセルデータが入ったポインタのアドレスだが、
 メモリを確保する必要も解放する必要もない。
 なぜなら、g_hBmp で、既にそのピクセルデータ用のメモリ領域が確保されており、
 g_Dib.dsBm.bmBits は、そのポインタとメモリ領域を共有しているに過ぎないからである。
 DeleteObject(g_hBmp); で、そのメモリ領域は解放されるため、解放の必要がないのである。
 BYTE *pBuf = (BYTE*) g_Dib.dsBm.bmBits; というのもメモリ領域の共有である。
 ポインタの先頭アドレスをインクリメントやデクリメントで移動したい場合も、メモリ領域の共有を使う。
 pBuf の先頭アドレスを動かしても、g_Dib.dsBm.bmBits のそれは動かない。
 先頭アドレスの移動幅は、そのポインタの変数型で決まる。
 例えば、DWORD 型のポインタを BYTE 単位で動かしたい場合は、メモリ領域共有の際に、BYTE 型でキャストする。
 ポインタのコピーと共有の使い分けは、既にそのメモリ領域が確保されているかどうかで決まる。
 同じデータのために、もう1つ領域を確保してもメモリの無駄遣いになるからである。
 
 
#include <windows.h>

HBITMAP     g_hBmp = NULL;
DIBSECTION  g_Dib;

LRESULT CALLBACK ProcWnd(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hInst,HINSTANCE hInstPrev,LPSTR lpCmdLine,int nCmdShow)
{
    // 省略
}

LRESULT CALLBACK ProcWnd(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_CREATE:
        g_hBmp = (HBITMAP) LoadImage(NULL, "sample.bmp", IMAGE_BITMAP, 0, 0, 
                                    LR_LOADFROMFILE | LR_CREATEDIBSECTION);
        if(g_hBmp){
            GetObject(g_hBmp, sizeof(DIBSECTION), &g_Dib);
        }
        return 0;
    case WM_PAINT:
        HDC         hDC;
        PAINTSTRUCT ps;
        hDC = BeginPaint(hWnd,&ps);
        if(g_hBmp){
            // 24bit色の場合は、上半分を青色で塗りつぶす
            // (ただし、ビットマップはボトムアップ形式のため、描画は下半分)
            if(g_Dib.dsBm.bmBitsPixel == 24){
                // バイト単位でアクセスするため、キャストする
                BYTE *pBuf    = (BYTE*) g_Dib.dsBm.bmBits;
                // 4 バイト境界幅にするためのパディング幅
                int  padBytes = g_Dib.dsBmih.biWidth % sizeof(DWORD);
                for(int y=0;y<g_Dib.dsBmih.biHeight/2;y++){
                    for(int x=0;x<g_Dib.dsBmih.biWidth;x++){
                        *pBuf ++ = 255; // 青
                        *pBuf ++ = 0;   // 緑
                        *pBuf ++ = 0;   // 赤
                    }
                    *pBuf += padBytes;
                }
            }
            // ビットマップの描画
            HDC     hDCcp1   = CreateCompatibleDC(hDC);
            HBITMAP hBmpPre1 = (HBITMAP) SelectObject(hDCcp1, g_hBmp);
            BitBlt(hDC, 0, 0, g_Dib.dsBmih.biWidth, g_Dib.dsBmih.biHeight, hDCcp1, 0, 0, SRCCOPY);
            SelectObject(hDCcp1, hBmpPre1);
            DeleteDC(hDCcp1);
        }
        EndPaint(hWnd,&ps);
        return 0;
    case WM_CLOSE:
        DeleteObject(g_hBmp);
        DestroyWindow(hWnd);
        PostQuitMessage(0);
        return 0;
    default:
        break;
    }
    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

22.メモリリーク

 メモリ容量表示ツール mem
 メモリリークは、メモリの浪費だけでなく、閉じたはずのファイルがプログラム終了まで他のプログラムから使用できなくなったりもします。
 deleteやfreeを忘れる場合は、確保と同時に削除のコードを書くようにすれば防ぐことが出来ますが、以下のような例はありがちで忘れやすいです。ビットマップが削除されない例とSTM_SETIMAGEで以前のビットマップが削除されない例です。
 他には、
  1. あるバッファを別のバッファで使用している場合などは、解放する順番を間違えると解放できない場合があります。
    確保とは逆順で解放することが多いようです。
  2. 確保した領域を解放しないで再確保しても以前の領域がメモリリークになります。
 アイコンは、CreateIconIndirect() で作成した場合は、DestroyIcon() で削除し、その他の関数で作成した場合は削除する必要なし。
× 悪い例:以下のプログラムを実行すると
      メモリリークが発生します。

HBITMAP hBmp;

void Load_Bmp(HBITMAP hBmp)
{
    hBmp = LoadBitmap(g_hInst,
           MAKEINTRESOURCE(IDB_BITMAP1));
}

void Smpl_Bmp()
{
    Load_Bmp(hBmp);
    DeleteObject(hBmp);
}
○ 良い例:引数にポインタを使うと
      BITMAPを削除できるようになります。

HBITMAP hBmp;

void Load_Bmp(HBITMAP *hBmp)
{
    *hBmp = LoadBitmap(g_hInst,
            MAKEINTRESOURCE(IDB_BITMAP1));
}

void Smpl_Bmp()
{
    Load_Bmp(&hBmp);
    DeleteObject(hBmp);
}
× 悪い例:以前のビットマップが上書き
      されてメモリリークになります。

HWND    hPic;
HBITMAP hBmp;
void Change_Bmp()
{
    if(hBmp)
        SendMessage(hPic,
            STM_SETIMAGE, IMAGE_BITMAP,
            (LPARAM) hBmp);
}


○ 良い例:以前のビットマップが削除されます。


HWND    hPic;
HBITMAP hBmp;
void Change_Bmp()
{
    HBITMAP hbm;
    if(hBmp)
        hbm = (HBITMAP) SendMessage(hPic,
               STM_SETIMAGE, IMAGE_BITMAP,
              (LPARAM) hBmp);
    if(hbm)    DeleteObject(hbm);
}
○ 良い例:ビットマップが削除されます。

char file[][MAX_PATH] = {"1.bmp","2.bmp","3.bmp"};
HBITMAP *hBmp = new HBITMAP [3];
HBITMAP hBmp2;
for(int i=0;i<3;i++){
    hBmp2 = (HBITMAP) LoadImage(NULL, file[i],
             IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
    hBmp[i] = hBmp2;
}
for(i=0;i<3;i++){
    DeleteObject(hBmp[i]);
}
delete [] hBmp;
// メモリリークの検出プログラム1
// 検出できるのは new 演算子だけ

#include <windows.h>
#include <stdio.h>
#include <iostream.h>

bool    fLogMemory = true;    // Perform logging (false=no; true=yes)?
int     cBlocksAllocated = 0; // Count of blocks allocated.

// User-defined operator new.
void *operator new( size_t stAllocateBlock )
{
    static fInOpNew = 0;    // Guard flag.

    if( fLogMemory && !fInOpNew )
    {
        fInOpNew = 1;
        ++ cBlocksAllocated;
        fInOpNew = 0;
    }
    return malloc( stAllocateBlock );
}

// User-defined operator delete.
void operator delete( void *pvMem )
{
    static fInOpDelete = 0;    // Guard flag.

    if( fLogMemory && !fInOpDelete )
    {
        fInOpDelete = 1;
        -- cBlocksAllocated;
        fInOpDelete = 0;
    }
    free( pvMem );
}

void Show_MemoryLeaks()
{
    char str[256];
    sprintf(str, "メモリリークは、%d 件です", cBlocksAllocated);
    MessageBox(GetFocus(), str, "",MB_OK);
}

int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR lpCmdLine, int nCmdShow)
{
    char *a;
    a = new char[1000];
    memcpy(a, "123456", 7);
//  delete [] a;
    Show_MemoryLeaks();
    return 0;
}

------------------------------------------------------------------------------------------

// メモリリークの検出プログラム2
// デバッグモードでビルドし、[ビルド]→[デバッグの開始]→[実行]を行う
// デバッグ情報に Detected memory leaks! があればリークが存在する
// GDIPLUS を使う場合は、#ifdef 〜 #endif までの3行を削除する
// GDIPLUS の new 演算子はデバッグの対象から外れる

#include <windows.h>
#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
#ifdef _DEBUG
#define new new(_NORMAL_BLOCK, __FILE__, __LINE__)
#endif

int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR lpCmdLine, int nCmdShow)
{
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
    
    char *p1;
    p1 = new char[1000];
    memcpy(p1, "123456", 7);
//  delete [] p1;
    char *p2;
    p2 = (char*)malloc(64);
    memcpy(p2, "abcdef", 7);
//  free(p2);
    return 0;
}

23.デバッグ

 コードに不具合があってもコンパイルが通ることがあります。

 Windows9x系とNT系のOS、それぞれのリリース版とデバッグ版の計4種類で動作確認をするとこのようなバグを減らせます。

 MSDEV で、[ビルド] をクリックし、[アクティブな構成の設定] をクリックし、どちらかを選択する。

24.マウス・キー入力

 WM_KEYDOWNはフォーカスのあるウィンドウにメッセージを送るので、ツールバー内のコンボボックスのアイテムを選択したときなどは、フォーカスをトップレベルウィンドウに戻すなどの対処が必要。
// メインウインドウプロシージャ
LRESULT CALLBACK procWin(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
    switch (msg)
    {	
    case WM_KEYDOWN:        // ゲームではGetAsyncKeyState()を使う
        switch ( wParam )
        {
        case VK_ESCAPE:     // [ESC]キーが押されたとき
            break;
        case 'C':           // [C]キーが押されたとき
            break;
        }
        break;
    case WM_LBUTTONDOWN:    // マウスの左ボタンが押されたとき
        int x,y;
        x = LOWORD(lParam); // x座標を取得
        y = HIWORD(lParam); // y座標を取得
        break;
    case WM_LBUTTONDBLCLK:  // ダブルクリックしたとき
        break;              // WNDCLASSEXで wc.style=CS_DBLCLKS; としておく必要がある。
    case WM_MOUSEMOVE:      // マウスが動いたとき
        char ss[256];
        sprintf(ss,"%d %d",LOWORD(lParam),HIWORD(lParam));
        SendMessage(hWnd, WM_SETTEXT, 0, (LPARAM) ss);
        break;
    case WM_CLOSE:
        DestroyWindow(hWnd);
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, uMsg, wParam, lParam);
    }
    return 0;
}

25.文字セット

文字は、コンピュータ内部では、文字コードで表現されます。
そのため、文字は、16進数の文字コードでも記述できます。
例えば、A の文字コードは 0x41 です。
  putchar('A');
としても
  putchar(0x41);
としても同じ A という文字が表示されます。

文字コードは各国の工業規格として決められています。
アメリカの工業規格団体は ANSI(アンシー)で、ASCII(アスキー)コードと同じものです。
日本規格はJIS、国際規格は ISO です。
どのコードも 7ビット(JIS は8ビット)で表現され、内容は(ISO の規格に沿って)ほぼ同じです。
7ビットまでで比較するならば、JIS と ANSI では、\ と \ が異なる程度です。
ANSI や ISO は、半角の英数字や記号で構成され、JIS にはそれに半角カタカナも含まれています。
漢字、ひらがな などの全角文字も表現できる文字コードもあります。
その文字コードには、
  Shift-JIS
  EUC
  UTF-8(Unicode)
  UTF-16(Unicode)
など、いくつかあり、Shift-JIS は Windows(95系) で、Unicode は Windows(NT系) で、EUC や UTF-8 は Unix や Linux でよく使われます。
2バイト文字のShift-JIS は、1バイト文字のJISと組み合わせて使います。
Visual C++ では、Shift-JIS と Unicode を使用できます。
OS は、Shift-JIS しか使えなくても、Visual C++ としては2種類使えるわけです。

Visual C++ は、アメリカ主体で作られているので、MSDN Library では ANSI という文字をよく見かけますが、日本においては JIS コードのことです。

Visual C++ では、文字コードごとに文字列操作関数が異なります。
また、その使用の際には、どれを使うか事前に定義する必要があります。
その種類を文字セットと呼びます。
文字コードに JIS + Shift-JIS を使う場合は、文字セットにシングルバイト文字セット(SBCS)または、マルチバイト文字セット(MBCS)を使います。
文字コードに Unicode を使う場合は、文字セットにユニコード(UNICODE)を使います。

定義なしSBCS
#define _MBCSマルチバイト文字を定義する場合
#define _UNICODE
#define UNICODE
Unicodeを定義する場合
定義は #include の前に行う。
#define の代わりに、[プロジェクト]→[〜のプロパティ]→[構成プロパティ]→[文字セット]で[Unicode 文字セットを使用する]などを選択しても良い。


【SBCS の場合】

SBCS では、strchr や strcmp などの関数を使います。
これらのシングルバイト文字列操作関数は、1バイト文字(JIS)はもちろん、2バイト文字(Shift-JIS)も1バイトずつ判定します。
strchr は、常に1バイトずつ文字を調べていくので、2バイト文字の2バイト目が検索したい文字と被ってしまっていると検索対象ではない2バイト文字の2バイト目を見つけることになります。
例えば、「ポ」の文字コードは 83 7C、「|」は 7C となっています。
この問題を避けるために、MBCS を使用できます。
SBCS は、char 型です。
#include <string.h> が必要。


【MBCS の場合】

MBCS では、1バイト文字か2バイト文字かを判定しながら文字列を操作する関数を使います。
MBCS は、unsigned char 型です。
#include <mbstring.h> が必要。
文字列長_mbclen、_mbslen...
比較_mbscmp...
位置_mbschr、_mbsstr...
連結_mbscat...
コピー_mbscpy...
変換MultiByteToWideChar、L""、mbtowc...
調査IsDBCSLeadByte...
他にどんな関数があるかは、
C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\include\mbstring.h
をテキストエディタで見ること。
<例>
#include <stdio.h>
#include <mbstring.h>

int main()
{
    char s1[32] = "ABCdEfG";
    char *s2 = (char*) _mbslwr((unsigned char*) s1);
    printf("%s\n", s2);
    return 0;
}


【UNICODE の場合】

NT系OS が対応。(9x では Unicode としてコンパイルされた実行ファイルが動かない)
Unicode 文字列を操作するときは、ワイド文字列操作関数を使用します。
WCHAR 型です。
#include <wchar.h> が必要。
文字列長wcslen...
比較wcscmp...
位置wcschr、wcsstr...
連結wcscat...
コピーwcscpy...
変換WideCharToMultiByte、_wtoi...
他にどんな関数があるかは、
C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\include\wchar.h
をテキストエディタで見ること。


char の代わりに TCHAR を、
char* の代わりに LPTSTR を、
const char* の代わりに LPCTSTR を、
L"" の代わりに、TEXT("") または _T("") を、
WinMain の代わりに、_tWinMain を
strlen、strcpy などの代わりに _tcslen_s、_tcscpy_s などを使うと定義に合わせて、SBCS、MBCS、UNICODE のそれぞれのデータ型、関数に自動変換してくれる。
他にどんな関数があるかは、
C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\include\tchar.h
をテキストエディタで見ること。

26.スピンコントロールの初期化

 スピンコントロールを0以外の値で初期化できないときは、WM_SHOWWINDOW で行うか、自動関連付けスタイルを無効にしてWM_INITDIALOGでUDM_SETBUDDYメッセージを送信した後で行う。
 あるいは、関連付けたエディットボックスに、WM_SETTEXT で初期化する。
case WM_INITDIALOG:
    {
    HWND hEdit1 = GetDlgItem(hDlg, IDC_EDIT1);
    SendDlgItemMessage(hDlg, IDC_SPIN1, UDM_SETRANGE, 0, MAKELONG(255, 0));
    SendDlgItemMessage(hDlg, IDC_SPIN1, UDM_SETBUDDY, (WPARAM) hEdit1, 0);
    SendDlgItemMessage(hDlg, IDC_SPIN1, UDM_SETPOS  , 0, MAKELONG(100, 0));
    }
    break;