昨日の今日ですが、なんでワイド文字化したウィンドウのタイトルがASCIIベースになるのかWEBを探っていたら、ひょんなことから中国語サイトの記事(末尾参照)に遭遇。(原因は想像していた通りでしたが、その先が思いつきませんでした。残念。)

 

ウィンドウを作るところをワイド文字対応させても、(迂闊でしたが)ウィンドウの維持するところが未処理であったことに気が付き、「あっ、そーか!!!そりゃそーだよね!」ということで解決しました。

ポイントはウィンドウを維持する黒子、舞台裏の「入口」(メッセージ処理)と「出口」(コールバック関数)がまだワイド文字対応に直していなかったことです。(しっかし、Microsoftの接尾語'A'と'W'の関数マクロ対応は、'W'版があるのかないのかが分からないことがネックですねー。)

また、BCCSkeltonのコールバック関数のマクロ処理にも'A'のDefWindowProc関数が使われていたので、BCCSkeltonWplusにマクロ展開されたコードのこれを再度DefWindowProcW関数に変換するマクロを追加して対応しました。(↓の変更点参照。)

 

その結果、例の追加コードですが、次のようになりました。

    //Test - 何故かL"WCEditor"のタイトルが"W"になったが今回の対応で治った
    CSTRW str = SendMsg(WM_GETTEXTLENGTH, 0, 0);
    str.Print();    
//ちゃんと"8"が表示される(↓)

    SendMsg(WM_SETTEXT, 0, (LPARAM)L"WCEditor");
    //再度タイトルを変更しても大丈夫

 

【CSDIWの改良点】

//メッセージループ
UINT CSDIW::Loop() {

    while(GetMessageW(&m_Msg, NULL, 0, 0)) {
        //モードレスダイアログがある場合
        if(!IsDialogMessageW(m_hDlg, &m_Msg) &&
        //アクセラレーター処理
            !TranslateAcceleratorW(m_hWnd, m_Accel, &m_Msg)) {
            TranslateMessage(&m_Msg);    //(解説:これだけはワイド文字版が無いんですよね。)
            DispatchMessageW(&m_Msg);
        }
    }
    return m_Msg.wParam;
}
 

【BCCSkeltonWplus.hの改良点】

/////////////////////////////////////////
//ウィンドウコールバック関数マクロ再変換
/////////////////////////////////////////

//SDIメッセージループの終了用マクロ・書式=END_WNDMSGのみ
#define    DefWindowProc    DefWindowProcW
 

【参考サイト】

program - C++:なぜこのウィンドウのタイトルは切り捨てられるのですか?

相互作用するすべてのAPI呼び出しの間で文字セットを一致させるのが理想的です。 したがって、 CreateWindowExWを使用する場合は、 RegisterClassExW (解説:対応済)、 DefWindowProcW(解説:未対応) 、 DispatchMessageW(解説:未対応) ...も使用する必要があります。」(解説:その他GetMessageW、IsDialogMessageW、TranslateAcceleratorWが未対応だった。)

PaintBoxでお茶を濁していましたが、余り次のテーマが思いつかないので、例のWCEditorを完全ワイド文字化しようとしました。

 

最後に残っていたのがメインウィンドウ(CSDI-CWNDから派生させているが、CWNDWは既に対応済)だったので、CWNDWから派生させてCSDIWを作りました。そしてWCEditorのCMyWndクラスをCSDIWから派生させます。(勿論VERSIONDLGもCDLGWから派生させます。)そして何から何までL"(文字列)"にしました。(末尾参照-ワイド化の変更部分(全部ではなく最初だけ)参照

 

そしてコンパイル。

 

きちんとコンパイルされ、正常に動きますが、何故かウィンドウタイトルが一文字しか表示されません(!?)

 

このウィンドウタイトルはCSDIWクラスでは触れておらず、CWNDWクラスのCreate関数(CreateWindowExを使用)でWCHAR*として与えており、CMyWndのインスタンスではWCEditor.cppでL"WCEditor"として与えています。為念、CWNDWクラスのCreate関数で引数、CreateWindowEx時の引数等をメッセージボックスで調べましたがきちんとワイド文字のL"WCEditor"になっています。

 

「何時、どの様にして1文字になるのか?」を調べるために、OnCreate関数(WM_CREATE)の段階でこんな(↓)コードを入れてみます。(なお、SendMsg関数はCWNDWクラスで既にワイド文字化しています。)
    //Test - 何故かL"WCEditor"のタイトルが"W"になる
    CSTRW str = SendMsg(WM_GETTEXTLENGTH, 0, 0);
    str.Print();   
//"1"が表示される
    SendMsg(WM_SETTEXT, 0, (LPARAM)L"WCEditor");
    
//再度タイトルを変更してもやはり1文字になる

 

常識的に考えるとワイド文字のL"WCEditor"の最初の"W(ASCII 0x57、Unicode 0x0057)"がリトルエンディアンでひっくり返り、0x57 0x00になっているので、これをchar*配列と間違えると"W"一文字の文字列に見えますが、全てユーザーサイドではワイド文字対応になっているので、メインウィンドウがタイトル(ウィンドウテキスト)をchar*として扱っているとしか考えられません。(なぜならば、OnCreate関数での↑の再度タイトルを変更する実験でウィンドウが1文字しか受け付けないからです。)

 

ウィンドウはCreateWindowExWでつくっているし、ShowWindowやUpdateWindowにはワイド文字対応が無いので、正直何をどうすればよいかわかりません。又々謎です。

 

ps. 謎が解けるまでは、公開プログラムサンプルのWCEditorには正常に動く前の版と、新しい完全ワイド化版を"WCEditorW"として両方入れておきます。(;´д`)トホホ

 

【CSDIW.h】

///////////////////////////////////////
// SDIウィンドウクラスCSDIW定義ファイル
///////////////////////////////////////

class CSDIW : public CWNDW
{
protected:
    HANDLE    m_hMutex;
    WCHAR    m_UniqName[MAX_PATH];

public:        //メンバー変数
    MSG            m_Msg;            //ウィンドウメッセージ
    HACCEL        m_Accel;        //Acceleratorの有無
    HWND        m_hDlg;            //IsDialogMessage()使用時に使う

public:        //メンバー関数
    CSDIW(WCHAR*);
    ~CSDIW();
    bool InclAccel(LPCWSTR);
    UINT Loop();
    bool IsOnlyOne();            //2重起動防止関数
};

//CSDIのコンストラクター
CSDIW::CSDIW(WCHAR* UName) {

    wsprintfW(m_UniqName, L"%d", UName);
}

//CSDIのデストラクター
CSDIW::~CSDIW() {

    if(m_hMutex)
        CloseHandle(m_hMutex);
}

//アクセラレーター
bool CSDIW::InclAccel(LPCWSTR Name) {

    if(!(m_Accel = LoadAcceleratorsW(m_hInstance, Name))) {
        MessageBoxW(NULL, L"アクセラレーターの読込に失敗しました",
                    L"エラー", MB_OK);
        return FALSE;
    }
    else
        return TRUE;
}

//メッセージループ
UINT CSDIW::Loop() {

    while(GetMessage(&m_Msg, NULL, 0, 0)) {
        //モードレスダイアログがある場合
        if(!IsDialogMessage(m_hDlg, &m_Msg) &&
        //アクセラレーター処理
            !TranslateAccelerator(m_hWnd, m_Accel, &m_Msg)) {
            TranslateMessage(&m_Msg);
            DispatchMessage(&m_Msg);
        }
    }
    return m_Msg.wParam;
}

bool CSDIW::IsOnlyOne() {

    m_hMutex = CreateMutexW(NULL, TRUE, m_UniqName);
    if (!m_hMutex)     return FALSE;
    // もしGetLastError()が、ERROR_ALREADY_EXISTSなら、
    // すでにアプリケーションが起動している
    if (GetLastError() == ERROR_ALREADY_EXISTS) {
    //二重起動時の処理
        return FALSE;
    }
    return TRUE;
}
 

【WCEditor.h抜粋】

/////////////////////////////////////////////////////////////////////
//CMyWndクラスをCSDIWクラスから派生させ、メッセージ用の関数を宣言する
/////////////////////////////////////////////////////////////////////
class CMyWnd : public CSDIW
{
public:    //以下はコールバック関数マクロと関連している
    //2重起動防止用のMutex用ID名称
    CMyWnd(WCHAR* UName) : CSDIW(UName) {}

///////////////////////////////////////////
// CDLGWクラスからVERSIONDLGクラスを派生
// 複数の同一ダイアログ変数とダイアログも作
// れるが、一つのダイアログに一つの派生ダイ
// アログクラスを作成するのが基本
///////////////////////////////////////////
class VERSIONDLG : public CDLGW {

 

【WCEditor.cpp抜粋】

    //ウィンドウ登録 - Init(ClassName, hInstance, WndProc, "IDM_MAIN",
    //                        (以下省略可)MAKEINTRESOURCE(IDI_ICON), IDC_ARROW, Brush)
    WCEditor.Init(L"MainWnd", hInstance, SDIPROC, L"IDM_MAIN", MAKEINTRESOURCEW(IDI_ICON));

    //ウィンドウ作成と表示-Create(WindowTitle, (以下省略可)Style,
    //                        ExStyle, hParent, hMenu, x, y, w, h)
    if(!WCEditor.Create(L"WCEditor"))
        return 0L;

 

では、最後にPaintBoxProc.hの解説です。

 

【PaintBoxProc.h】

//////////////////////////////////////////
// PaintBoxProc.h
// Copyright (c) 05/02/2022 by BCCSkelton
//////////////////////////////////////////

/////////////////////////////////
//主ウィンドウCMyWndの関数の定義
//ウィンドウメッセージ関数
/////////////////////////////////
bool CMyWnd::OnInit(WPARAM wParam, LPARAM lParam) {

    //PICTUREBOXの初期化
    PicBox.Init(m_hWnd, IDC_PICBOX);

//(解説:ここでCPICBOXクラスのPicBoxインスタンスとメインダイアログのIDC_PICBOXが連結され、仮想ウィンドウ(ビットマップ)が作成され、ダミーのコールバック関数から正規のコールバック関数を呼ぶことになります。)

    MessageBox(m_hWnd, "画面を消去するにはマウス左ボタンを押してください。\n"\
                "またマウス右ボタンを押すとメニューが出ます。",
                "操作方法", MB_OK | MB_ICONINFORMATION);
//(解説:ダサいのですが、仕様に関わる初期指示をメッセージボックスで出しました。)

    return TRUE;
}

bool CMyWnd::OnClose(WPARAM wParam, LPARAM lParam) {

    if(MessageBox(m_hWnd, "終了しますか", "終了確認",
                    MB_YESNO | MB_ICONINFORMATION) == IDYES)
        //処理をするとDestroyWindow、PostQuitMessageが呼ばれる
        return TRUE;
    else
        //そうでなければウィンドウではDefWindowProc関数をreturn、ダイアログではreturn FALSEとなる。
        return FALSE;
}

bool CMyWnd::OnNotify(WPARAM wParam, LPARAM lParam) {

    //PICTUREBOX内のマウス情報はWM_NOTIFYを使い、
    //1)nmhdr.hwndFromはウィンドウハンドル
    //2)nmhdr.codeでマウスメッセージを受け、
    //3)wParamはマウス座標を送り
    //4)マウス情報(wParam)はnmhdr.idFromで送る
    NMHDR* lpnmhdr = (NMHDR*)lParam;
    if(lpnmhdr->code == WM_LBUTTONDOWN) {
        PicBox.Color(15);
        PicBox.Clear();
    }
    else if(lpnmhdr->code == WM_RBUTTONDOWN) {
        //ポップアップメニューの読込
        HMENU hMenu = LoadMenu(m_hInstance, "IDM_POPUP");
        HMENU hPopupMenu = GetSubMenu(hMenu, 0);
        //ウインドウの位置情報を取得
        RECT rec;
        GetWindowRect(m_hWnd, &rec);
        //ポップアップメニューの表示場所を設定
        POINT pt;
        GetCursorPos(&pt);
        //ポップアップメニューの表示
        TrackPopupMenu(hPopupMenu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_LEFTBUTTON, pt.x, pt.y, 0, m_hWnd, &rec);
        //注:このままではポップアップメニューのメニューアイテムを選んだ後も、メニューが消えないので
        //  IDC_PICBOXコントロールの座標(0, 0)でマウスの左ボタンが押されたように見せかける。
        SendItemMsg(IDC_PICBOX, WM_LBUTTONDOWN, MK_LBUTTON, 0);
        //ポップアップメニューの開放
        DestroyMenu(hMenu);
    }

//(解説:ISC_PICBOXコントロール上でマウスを左クリックすると画面を白色で消去します。右クリックするとIDM_POPUPメニューを表示します。この際、↑の注にあるようにメニューアイテムを選択してもポップアップメニューが消えない症状が出たので、左ボタンが押されたこと(従って既存画面は消去されます)をメッセージとして伝えるようにしてごまかしています。なお、呼び出したメニューは破棄しないとメモリーリークにつながりますのでご注意を。)

   return TRUE;
}

//ダイアログベースの場合はこれが必要
bool CMyWnd::OnDestroy(WPARAM wPram, LPARAM lParam) {

    PostQuitMessage(0);
    return TRUE;
}

/////////////////////////////////
//主ウィンドウCMyWndの関数の定義
//メニュー項目、コントロール関数
/////////////////////////////////
bool CMyWnd::OnRect() {

    //16色の正方形の重ね描画
    for(int i = 0; i < 16; i++) {
        PicBox.Color(i);
        PicBox.Box(10 + i * 10, 10 + i * 10, 320 - i * 10, 320 - i * 10, 1);
    }
    return TRUE;
}

//(解説:単純な正方形の重ね表示です。)


bool CMyWnd::OnSpiral() {

    //「新Visual Basic入門」P309より移植
    int xo = 150, yo = 200;
    double deg;
    double x, y = 100, r_large = 100, r_small = 60, shift = 0;
    for(deg = 0; deg < 360 * 3; deg += 3) {
        x = r_large * cos(deg * g_RAD);
        y = r_large * sin(deg * g_RAD);
        x += xo + shift++;
        y += yo;
        PicBox.Color(1);
        PicBox.Ellipsed(x - r_small, y - r_small, x + r_small, y + r_small, 1);
        PicBox.Color(15);
        PicBox.Ellipsed(x - r_small + 1, y - r_small + 1, x + r_small - 1, y + r_small - 1, 1);
        if(deg > 360 * 2)
            r_small *= 0.98;
    }

//(解説:半径が漸減する円が渦を巻いて行きます。BCBでは外周をPenで描画し、中をBrushで塗りつぶしましたが、CPICBOXでは中野塗りつぶしは白色の円を描画しています。)

    return TRUE;
}

bool CMyWnd::OnHirbelt() {

    //「新Visual Basic入門」P311より移植-ヒルベルト曲線
    int x, y, ct, size = 256;        // 描画全体サイズ(16 ^ 3)
    int len, d;
    char str[128];
    for(ct = 1; ct < 7; ct++) {        // 位相1, 2までは正常
        PicBox.Color(15);
        PicBox.Clear();
        len = size / pow((float)2, ct);    //線の長さ
        x = size - size / pow((float)2, ct + 1);
        y = size / pow((float)2, ct + 1);
        d = 270;                    //向き角度
        DrawA(ct - 1, x, y, d, len);//Starting drawing
        //オリジナルのSleep(500);だと第1-5の位相が描画されない。
        wsprintf(str, "第%dの位相です。", ct);
        MessageBox(m_hWnd, str, "位相", MB_OK);
    }

//(解説:BCBではSleep()関数を使いましたが、CPICBOXでは何故か途中描画が(別途InvalidateRectを入れても)表示されず、MessageBoxにすると途中描画が見えるのでそうしました。)

    return TRUE;
}

bool CMyWnd::OnTree() {

    int n = 4, m;
    double len = g_LEN, th = 180 / 2, deg = 180 / n, x = g_XSIZE / 2, y = g_YSIZE / 3;
    PicBox.Color(4);
    PicBox.Line(x, g_YSIZE, x, g_YSIZE - y);    //Initial main branch
    Tree(x, y, len, th, deg);
    return TRUE;

//(解説:これはBCBのままですね。)

}

bool CMyWnd::OnRotate() {

    int m, n, i, j, len = 150, x1, y1, x2, y2;
    double PAI = 3.14159, th, th1, th2;
    for (n = m = 5; n < 15; m++, n++) {
        PicBox.Color(15);
        PicBox.Clear();
        PicBox.Color(3);
        for(i = 0; i < m; i++) {
            for(j = 1; j < n; j++) {
                th = j * (PAI / n);
                th1 = i * (PAI / m) + th;
                th2 = i * (PAI / m) - th;
                x1 = cos(th1) * len + 200;
                y1 = sin(th1) * len + 200;
                x2 = cos(th2) * len + 200;
                y2 = sin(th2) * len + 200;
                PicBox.Line(x1, y1, x2, y2);
            }
        }
        //オリジナルのSleep(500);だと第1-5の位相が描画されない。
        MessageBox(m_hWnd, "OKボタンを押してください", "線形の回転", MB_OK);
        //Sleep(500);
    }

//(解説:これもBCBではSleep()関数を使いましたが、CPICBOXでは何故か途中描画が(別途InvalidateRectを入れても)表示されず、MessageBoxにすると途中描画が見えるのでそうしました。)

    return TRUE;
}

bool CMyWnd::OnSphere1() {

    int const RADIUS = 150, XBASE =200, YBASE = 200;
    double x, y, z, x2, y2, degz, degy;
    PicBox.Color(8);
    for(degz = -90; degz <= 90; degz += 10) {
        x = RADIUS * cos(degz * g_RAD);
        y = RADIUS * sin(degz * g_RAD);
        for(degy = 0; degy <= 360; degy++) {
            x2 = x * cos(degy * g_RAD);
            z = x * sin(degy * g_RAD);
            y2 = y * cos(30 * g_RAD) + z * cos(60 * g_RAD);
            PicBox.Dot(XBASE + x2, YBASE + y2);
        }
    }
    for(degy = 0; degy <= 180; degy += 10) {
        for(degz = -90; degz <= 270; degz++) {
            x = RADIUS * cos(degz * g_RAD);
            y = RADIUS * sin(degz * g_RAD);
            x2 = x * cos(degy * g_RAD);
            z = x * sin(degy * g_RAD);
            y2 = y * cos(30 * g_RAD) + z * cos(60 * g_RAD);
            PicBox.Dot(XBASE + x2, YBASE + y2);
        }
    }

//(解説:これはBCBではPixcel関数で直接ピクセル操作をしていますが、CPICBPXではDot関数で対応しました。)

    return TRUE;
}

bool CMyWnd::OnSphere2() {

    int const RADIUS = 150, XBASE =200, YBASE = 200;
    int const R2 = 150 * 149;
    double x, y, z, x2, y2, degz, degy;
    int Draw, InsideFlag;
    for(degz = 90; degz >= -90; degz -= 10) {
        x = RADIUS * cos(degz * g_RAD);
        y = RADIUS * sin(degz * g_RAD);
        for(degy = -90; degy <= 90; degy += 0.5) {
            x2 = x * cos(degy * g_RAD);
            z = x * sin(degy * g_RAD);
            y2 = y * cos(30 * g_RAD) + z * cos(60 * g_RAD);
            if(degz >= -50) {
                PicBox.Color(15);
                PicBox.Line(XBASE - x2, y2 + YBASE, XBASE + x2, y2 + YBASE);
            }
            PicBox.Color(5);
            PicBox.Dot(XBASE + x2, YBASE + y2);
            PicBox.Dot(XBASE - x2, YBASE + y2);
        }
    }
    for(degy = 0; degy <= 180; degy += 10) {
        Draw = InsideFlag = TRUE;
        for(degz = -90; degz <= 270; degz += 0.5) {
            x = RADIUS * cos(degz * g_RAD);
            y = RADIUS * sin(degz * g_RAD);
            x2 = x * cos(degy * g_RAD);
            z = x * sin(degy * g_RAD);
            y2 = y * cos(30 * g_RAD) + z * cos(60 * g_RAD);
            if((x2 * x2 + y2 * y2) >= R2) {
                if(InsideFlag == TRUE)
                    Draw = !Draw;
                InsideFlag = FALSE;
            }
            else
                InsideFlag = TRUE;
            if(Draw == TRUE || InsideFlag == FALSE) {
                PicBox.Color(5);
                PicBox.Dot(XBASE + x2, YBASE + y2);
            }
        }
    }

//(解説:これもBCBではPixcel関数で直接ピクセル操作をしていますが、CPICBPXではDot関数で対応し、裏側の消去はLine関数を使っています。)

    return TRUE;
}

bool CMyWnd::OnExit() {

    SendMsg(WM_CLOSE, 0, 0);
    return TRUE;
}

///////////////////
//ユーザー定義関数
///////////////////
bool CMyWnd::DrawA(int ct, int& x, int& y, int d, int len) {
    if((ct) < 0)
        return FALSE;
    d -= 90; DrawB(ct - 1, x, y, d, len); Drawline(x, y, d, len);
    d += 90; DrawA(ct - 1, x, y, d, len); Drawline(x, y, d, len);
    DrawA(ct - 1, x, y, d, len); d += 90; Drawline(x, y, d, len);
    DrawB(ct - 1, x, y, d, len); d -= 90;
    return TRUE;
}

bool CMyWnd::DrawB(int ct, int& x, int& y, int d, int len) {
    if(ct < 0)
        return FALSE;
    d += 90; DrawA(ct - 1, x, y, d, len); Drawline(x, y, d, len);
    d -= 90; DrawB(ct - 1, x, y, d, len); Drawline(x, y, d, len);
    DrawB(ct - 1, x, y, d, len); d -= 90; Drawline(x, y, d, len);
    DrawA(ct - 1, x, y, d, len); d += 90;
    return TRUE;
}

void CMyWnd::Drawline(int& x, int& y, int& d, int len) {
    int oldx = x, oldy = y;
    if(d < 0)
        d += 360;
    if(d >= 360)
        d -= 360;
    switch(d) {
    case   0: x += len; break;
    case  90: y -= len; break;
    case 180: x -= len; break;
    case 270: y += len; break;
    }
    PicBox.Color(2);
    PicBox.Line(oldx, oldy, x, y);
}

bool CMyWnd::Tree(double x, double y, double len, double th, double deg) {
    double x1, y1, x2, y2, th1, th2;
    if(len <= g_LEN / 15)
        return FALSE;
    len *= 0.75;
    th1 = th - deg;
    th2 = th + deg;
    x1 = x + len * cos(th1 * g_RAD);
    y1 = y + len * sin(th1 * g_RAD);
    x2 = x + len * cos(th2 * g_RAD);
    y2 = y + len * sin(th2 * g_RAD);
    PicBox.Color(4);
    PicBox.Line(x, g_YSIZE - y, x1, g_YSIZE - y1);    //Right branch
    Tree(x1, y1, len, th1, deg);
    PicBox.Line(x, g_YSIZE - y, x2, g_YSIZE - y2);    //Left branch
    Tree(x2, y2, len, th2, deg);
    return TRUE;
}

//(解説:ユーザー定義関数はBCBのままです。)

 

この他、BCB版では最後のボタンが「画面消去」、システムの「X」ボタンで「プログラム終了」となっていましたが、BCCSkelton版ではマウス左クリックを「画面消去」にあて、マウス右クリックのポップアップメニューを追加し、最後のボタンを「プログラム終了」にしています。

後はコンパイルするだけです。

【PaintBox.bat】

@ECHO OFF
ECHO ----------------------------------
ECHO  BatchGood - Batch File Generator
ECHO  for Embarcadero "bcc32c.exe"
ECHO  Copyright (c) 2021 by Ysama
ECHO ----------------------------------

cd "C:\Users\(パス)\PaintBox\Debug"
"C:\Borland\BCC102\bin\bcc32c.exe" "C:\Users\(パス)\PaintBox\PaintBox.cpp" -tW -O1 -w- -I"C:\Borland\BCCForm"
del "C:\Users\(パス)\PaintBox\Debug\PaintBox.tds"
"C:\Borland\BCC102\bin\brc32.exe" "C:\Users\(パス)\PaintBox\PaintBox.rc" "C:\Users\(パス)\PaintBox\Debug\PaintBox.exe"
del "C:\Users\(パス)\PaintBox\PaintBox.res"
pause

 

これらの他にも面白いヒルベルト曲線などがありますので、入れ替えや追加を行って遊んでください。

 

前回リソース編で、ダイアログコントロールに未だ存在しないウィンドウクラス"PICTUREBOX"のコントロールを入れました。

今回はそれがどのようにして実装されるのかを見てゆきます。

 

先ず、CPICBOXクラスはプログラムの頭、即ちPicBox.hで外部変数として宣言されますので、プログラムが開始されるとCPICBOXのコンストラクターが働きますが、ウインドウクラスを登録するRegisterClass関数はやはりインスタンスが必要になります。その為、素直に(メインダイアログが生成されて)ウィンドウクラス"PICTUREBOX"が呼ばれる前にウインドウクラスを登録する様にします。

 

【PaintBox.cpp】

//////////////////////////////////////////
// PaintBox.cpp
//Copyright (c) 05/02/2022 by BCCSkelton
//////////////////////////////////////////
#include    "PaintBox.h"
#include    "PaintBoxProc.h"

////////////////
// WinMain関数
////////////////
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPSTR lpCmdLine, int nCmdShow) {

    //2重起動防止
    if(!PaintBox.IsOnlyOne()) {
        HWND hWnd = FindWindow("MainWnd", "PaintBox");
        if(IsIconic(hWnd))
            ShowWindow(hWnd, SW_RESTORE);
        SetForegroundWindow(hWnd);
        return 0L;
    }

    //CPICBOXクラスを登録する
    if(!PicBox.Register(hInstance))
        MessageBox(NULL, "PictureBoxが登録できませんでした", "警告", MB_OK | MB_ICONSTOP);

//(解説:↑で言っていること(hInstanceがあり、メインダイアログの生成前の場所)は、具体的なコードでこうなります。)


    //モードレスダイアログを作成Create(hParent, DlgName, DlgProc);    //←ここでダイアログが生成される
    if(!PaintBox.Create(NULL, hInstance, "IDD_MAIN", ModelessProc))
        return 0L;

    //メッセージループに入る
    return PaintBox.Loop();
}
 

【PaintBox.h】

//////////////////////////////////////////
// PaintBox.h
// Copyright (c) 05/02/2022 by BCCSkelton
//////////////////////////////////////////
//BCCSkeltonのヘッダー-これに必要なヘッダーが入っている
#include    "BCCSkelton.h"
//リソースIDのヘッダー
#include    "ResPaintBox.h"
//浮動小数点計算ライブラリー
#include <math.h>

//Tree用の定数
const double g_RAD = 3.14159 / 180;
double const g_XSIZE = 500, g_YSIZE = 400, g_LEN = 100;

//(解説:ヒルベルト曲線等を書くので、浮動小数点を使うのと、必要な定数をここで宣言しています。)

    
/////////////////////////////////////////////////////////////////////
//CMyWndクラスをCDLGクラスから派生させ、メッセージ用の関数を宣言する
/////////////////////////////////////////////////////////////////////
class CMyWnd : public CDLG
{
public:    //以下はコールバック関数マクロと関連している
    //2重起動防止用のMutex用ID名称
    CMyWnd(char* UName) : CDLG(UName) {}
    //メニュー項目、ダイアログコントロール関連
    bool OnRect();
    bool OnSpiral();
    bool OnHirbelt();
    bool OnTree();
    bool OnRotate();
    bool OnSphere1();
    bool OnSphere2();
    bool OnExit();
    //ウィンドウメッセージ関連
    bool OnInit(WPARAM, LPARAM);
    bool OnNotify(WPARAM, LPARAM);
    bool OnClose(WPARAM, LPARAM);
    bool OnDestroy(WPARAM, LPARAM);
    //ユーザー定義関数
    bool DrawA(int, int&, int&, int, int);
    bool DrawB(int, int&, int&, int, int);
    void Drawline(int&, int&, int&, int);
    bool Tree(double, double, double, double, double);

//(解説:ダイアログの動きは特殊なことはないのですが、描画は特殊なのでユーザー定義関数を宣言します。)

};

////////////////////////////////////////////////////////////////////////
//派生させたCMyWndクラスのインスタンスとコールバック関数(マクロ)の作成
//主ウィンドウはダイアログと違い、コールバック関数は一つしか作れない
////////////////////////////////////////////////////////////////////////
CMyWnd PaintBox("PaintBox");    //ウィンドウクラスインスタンスの生成

BEGIN_MODELESSDLGMSG(ModelessProc, PaintBox)    //コールバック関数名は主ウィンドウの場合ModelessProcにしている
    //メニュー項目、ダイアログコントロール関連
    ON_COMMAND(PaintBox, IDC_RECT, OnRect())
    ON_COMMAND(PaintBox, IDC_SPIRAL, OnSpiral())
    ON_COMMAND(PaintBox, IDC_HIRBELT, OnHirbelt())
    ON_COMMAND(PaintBox, IDC_TREE, OnTree())
    ON_COMMAND(PaintBox, IDC_ROTATE, OnRotate())
    ON_COMMAND(PaintBox, IDC_SPHERE1, OnSphere1())
    ON_COMMAND(PaintBox, IDC_SPHERE2, OnSphere2())
    ON_COMMAND(PaintBox, IDC_EXIT, OnExit())
    //ウィンドウメッセージ関連
    //自動的にダイアログ作成時にOnInit()、終了時にOnClose()を呼びます
    ON_NOTIFY(PaintBox)
    ON_DESTROY(PaintBox)
END_DLGMSG

///////////////////
//PictureBoxの作成
///////////////////
CPICBOX PicBox;

//(解説:ここでCPICBOXクラスの外部変数を宣言します。)

 

次回はPaintBoxProc.hで↑の関数の実装を解説します。

 

では、PaintBoxの解説をはじめましょう。

最も大事な「CPICBOXというC++のクラス」によってつくられる、「PICTUREBOXというウィンドウクラス」に注意してください。(注)

注:Win32のウィンドウズプログラムを始める際によく混乱しますが、C++のオブジェクトクラス(プロパティとメソッドの集合体)は、特定のウィンドウ(コントロールやメニュー等もその一つですが)を「包む(wrap)」ものですが、RegisterClass関数で登録するウィンドウには種別があり、よく「(WNDCLASS構造体).lpszClassName = "(なんちゃら)";」などと書くあの「なんちゃら」が"EDIT"や"BUTTON"などの種別名なんです。

 

【PaintBox.rc】

//-----------------------------------------
//             BCCForm Ver 2.41
//    An Easy Resource Editor for BCC
//  Copyright (c) February 2002 by ysama
//-----------------------------------------
#include    "ResPaintBox.h"

//----------------------------------
// ダイアログ (IDD_MAIN)
//----------------------------------
IDD_MAIN DIALOG DISCARDABLE 0, 0, 486, 308
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | DS_SETFONT | DS_CENTER
CAPTION "PaintBox"
FONT 8, "Times New Roman"
{
 CONTROL "Rectangular", IDC_RECT, "BUTTON", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 6, 4, 54, 14
 CONTROL "Spiral", IDC_SPIRAL, "BUTTON", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 66, 4, 54, 14
 CONTROL "Hirbelt curve", IDC_HIRBELT, "BUTTON", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 126, 4, 54, 14
 CONTROL "Tree", IDC_TREE, "BUTTON", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 186, 4, 54, 14
 CONTROL "Rotate lines", IDC_ROTATE, "BUTTON", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 246, 4, 54, 14
 CONTROL "Sphere1", IDC_SPHERE1, "BUTTON", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 306, 4, 54, 14
 CONTROL "Sphere2", IDC_SPHERE2, "BUTTON", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 366, 4, 54, 14
 CONTROL "Exit", IDC_EXIT, "BUTTON", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 426, 4, 54, 14
 CONTROL "", IDC_PICBOX, "PICTUREBOX", WS_CHILD | WS_VISIBLE | WS_BORDER, 6, 22, 474, 280, WS_EX_CLIENTEDGE
}

//--------------
// Popupメニュー
//--------------
IDM_POPUP MENU DISCARDABLE
{
    POPUP "Popup(&P)"
    {
        MENUITEM "Rectangular (&R)", IDC_RECT    //(解説:色違いの正方形を描きます。)
        MENUITEM "Spiral (&S)", IDC_SPIRAL    //(解説:徐々に小さくなる円で渦を描きます。)
        MENUITEM "Hirbelt curve (&H)", IDC_HIRBELT    //(解説:ヒルベルト曲線を描きます。)
        MENUITEM "Tree (&T)", IDC_TREE    //(解説:やはりフラクタル図形のTreeを描きます。)
        MENUITEM "Rotate lines (&L)", IDC_ROTATE    //(解説:対角線付きの5~14角形の回転を描きます。)
        MENUITEM "Sphere1 (&1)", IDC_SPHERE1    //(解説:円周線付の透明な球体を描きます。)
        MENUITEM "Sphere2 (&2)", IDC_SPHERE2    //(解説:円周線付の不透明な球体を描きます。)
        MENUITEM SEPARATOR
        MENUITEM "Close (&C)", IDC_EXIT
    }
}

//--------------------------
// イメージ(IDI_ICO)
//--------------------------
IDI_ICON    ICON    DISCARDABLE    "C:\Users\(パス)\PaintBox\Icon.ico"

IDD_MAINダイアログのコントロールとして"PICTUREBOX"というクラス名のコントロールを書いていますが、まだこの段階ではこの"PICTUREBOX"というコントロールは存在していません。(従ってこのままで行けばエラーになります。)

 

なお、IDM_POPUPというメニューは、右マウスボタンでポップアップさせるメニューです。(オリジナルにはありませんでしたが...)

 

世はコロナ禍とはいえ、ゴールデンウィーク。とうとうフラストレーションが爆発したのか、高速道路は渋滞しているようです。

そんな中で、ワイド文字も一服し、次は何をやろうか考え中の私。(余談ですが実は、27の時にGWなので彼女と富士五湖へドライブに行くため、朝6時に出発し、やっと着いたら既に渋滞しているので即帰路へ。結局路肩等を走っても夜の11:30'に彼女の家に着く始末。それから彼女、改め女房と「GWは出かけるのはよそう」という了解ができました。)

 

ということで、思いつく話題として「ダイアログコントロールを自作するにはどうすればよいか?」などを考えてみます。

 

ダイアログコントロールとは、ダイアログを親ウィンドウとしてその中に閉じ込められる子ウィンドウですね。「親だ、子だ」というと、CreateWindow関数のHWND hWndParent引数と、ウィンドウスタイルのWS_CHILDを思い出しますよね?どのような効果があるのか、どう動作が変わるのか、それぞれ実験してみると面白いですよ。なお、WS_CHILDを指定した際には「子のID」をCreateWindow関数のhMenuに指定しますが、「あのですねぇ、これ絶対に言わないでくださいよ。実は、メニューは子ウィンドウ、PopUpメニューは孫ウィンドウ、ひ孫ウィンドウ等なんですよ。」ということが分かります。ダイアログにはLABEL、EDITやBUTTON等様々なコントロールがありますが、SDI等のメインウィンドウにもメニュー、ツールバー、ステータスバーなどのコントロールが使われているんですよね。

 

さて、ではそんなコントロールを自分でつくれるのでしょうか?答えはYesです。

 

最も身近なカスタムコントロールの作り方としては、BCCSkeltonのコードでmお馴染みのコモンコントロールが思い浮かびますね。様々なコモンコントロールはcomctl32.dllというDLLに入っています。そしてこのDLLがアップロードされる際にインスタンス情報を受け取り(BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved))、そのインスタンス情報を使ってコモンコントロールのウィンドウクラスの登録(RegisterClass関数-インスタンス情報の他にコールバック関数アドレスも必要です)を行い、親のWM_CREATEまたはWM_INITDIALOG通知の段階で初期化(InitCommonControls関数)を行うシーケンスとなります。

 

これをBCCSkeltonで行えないか、ということで、習作としてCPICBOXクラスというものを20年前に作りました。CPICBOXクラスは、

(1)ダイアログのリソースにはコントロールのウィンドウクラス名として"PICTUREBOX"を指定し、

(2)ダイアログが生成される(即ちDialogBoxやCreateDialogという関数の実行)前に"PICTUREBOX"というウィンドウクラスを親のWinMain関数のインスタンス情報と(staticにしてアドレスが特定できるダミーのコールバック関数で登録し、

(3)同時に取得できたウィンドウハンドルから、ウィンドウプロパティ情報(注)にCPICBOXのインスタンスのアドレス(this)を書き込み、

(4)親のWM_CREATEまたはWM_INITDIALOGメッセージの段階で、呼ばれたダミーのコールバック関数でCPICBOXのインスタンスのアドレス(this)を読みだして、インスタンス内で確定した真正なコールバック関数を呼び出す

という方法を取っています。

注:

 

なんでこんな方法をとっているか、疑問に感じられる方もいるでしょう。実は最初はPICBOXのインスタンスができたならば、簡単にインスタンス内のコールバック関数が特定できると思っていたのですが、実際には何度やってもタイムラグがあるのかコールバック関数アドレスの取得に失敗しました。その結果、一旦はダミーで登録し、後で自分のアドレスを読みだしてコールバック関数を呼び出す、という↑の方法を思いついたと記憶しています。(コードは参考までに末尾に掲載)

 

いずれにしても、このような形で簡単にダイアログコントロールが作れるということが分かりました。これが実際に使える、ということを示すために、昔Visual Basicの教本に出ていたグラフィックサンプルをBorland C++ Builder 5.0に移植したものを再度CPICBOXを使ったダイアログに移植してみようと思います。乞うご期待。

 

【PaintBox-BCB 5.0版】

【PaintBox-BCCSkelton版】

 

【CPICBOXクラス】

/////////////////////////////////////////////
//Canvasを改造したPictureBoxクラス(CPICBOX)
//ダイアログコントロールに使用すると重宝する
//【使い方】
//1.グローバル変数に使いたいだけ宣言する
//2.<プロジェクト.cpp>ファイルのWinMain
//    でModelessダイアログを作る前にRegister
//    関数を呼ぶ
//3.WM_INITDIALOG(OnInit関数)でこのクラス
//    のInit関数を呼ぶ
//4.後はCanvasと同じ関数がPICTUREBOXコント
//    ロールで使えるようになる
//5.PICTUREBOX内ののマウス情報はWM_NOTIFY
//    を使い、
//   1)nmhdr.hwndFromはウィンドウハンドル
//   2)nmhdr.codeでマウスメッセージを受け、
//   3)wParamはマウス座標を送り
//   4)マウス情報(wParam)はnmhdr.idFromで送る
/////////////////////////////////////////////
class CPICBOX :
public CANVAS
{
public:
    HWND        m_hParent;        //PictureBoxコントロールの親ウィンドウハンドル
    HINSTANCE    m_hInstance;    //親ウィンドウのインスタンス

    CPICBOX();                    //コンストラクター
    bool Register(HINSTANCE);    //"PICTUREBOX"ウィンドウクラス登録
    bool Init(HWND, int);        //初期化(含むコールバック関数の設定)
    HWND GethWnd() {return m_hWnd;}    //ウィンドウハンドルを取得

    static LRESULT CALLBACK DummyProc(HWND, UINT, WPARAM, LPARAM);    //ダミーのコールバック関数
    virtual LRESULT CALLBACK PicBoxProc(HWND, UINT, WPARAM, LPARAM);//本来のコールバック関数
};

//コンストラクター
CPICBOX::CPICBOX() {

    m_hParent    = 0;            //親ウィンドウ
    m_hInstance    = 0;            //親のインスタンス
}

//"PICTUREBOX"ウィンドウクラスの登録
bool CPICBOX::Register(HINSTANCE
hInstance) {

    WNDCLASS wc;
    wc.style            = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc        =
this->DummyProc;    //ダミーコールバック関数
    wc.cbClsExtra        = 0;
    wc.cbWndExtra        = 0;
    wc.hInstance        = m_hInstance =
hInstance;
    wc.hIcon            = NULL;
    wc.hCursor            = LoadCursor(NULL, IDC_CROSS);
    wc.hbrBackground    = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wc.lpszMenuName        = NULL;
    wc.lpszClassName    = "PICTUREBOX";

    return RegisterClass(&wc);
}

//PICTUREBOXコントロールの初期化(
含むコールバック関数の設定
bool CPICBOX::Init(HWND hParent, int ID) {

    if(!hParent) {
        MessageBox(NULL, "ウィンドウハンドルが指定されていません",
                    "エラー", MB_OK | MB_ICONEXCLAMATION);
        return FALSE;
    }
    m_hWnd = GetDlgItem(m_hParent = hParent, ID);    //ウィンドウハンドルを記録
    //
ウィンドウプロパティにこのクラスのインスタンスへのポインターを書込む(重要)
    SetProp(m_hWnd, "CPP_CLASS", (HANDLE)this);
    HDC hDC = GetDC(m_hWnd);                        //対象ウィンドウのDCを取得
    m_hDC = CreateCompatibleDC(hDC);                //コンパチDCを作成
    m_Left = m_Top = 0;                                //フルスクリーンに拡大した
    m_Right = GetSystemMetrics(SM_CXFULLSCREEN);    //時のキャンヴァスサイズを
    m_Bottom = GetSystemMetrics(SM_CYFULLSCREEN);    //記録しておく

    m_hCanvas = CreateCompatibleBitmap(hDC,            //m_hDCではないので注意
                m_Right,                            //m_hDCにするとモノクロとなる
                m_Bottom);
    SelectObject(m_hDC, m_hCanvas);                    //キャンヴァスの実体
//    m_Pen = (HPEN)GetStockObject(BLACK_PEN);
    m_PStyle = PS_SOLID;                            //ソリッドペン
    m_PWidth = 0;                                    //1ピクセル
//    m_Brush = (HBRUSH)GetStockObject(WHITE_BRUSH);
    m_Color = RGB(0, 0, 0);                            //黒
    PatBlt(m_hDC, m_Left, m_Top, m_Right, m_Bottom, PATCOPY);    //初期描画
    ReleaseDC(m_hWnd, hDC);                            //hDCを開放
    m_Focus = FALSE;                                //DragRectフラグの初期化
    m_hFont = 0;                                    //m_hFontの初期化

    return TRUE;
}

//ダミーコールバック関数
LRESULT CALLBACK CPICBOX::DummyProc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam) {

    //
Initメンバー関数が書き込んだこのクラスのインスタンスへのポインターを読む
    CPICBOX* pPicBox = (CPICBOX*)GetProp(hWnd, "CPP_CLASS");
    //もしそれが読み込めたら
    if(pPicBox) {
        //
本来のコールバック関数(PicBoxProc)を呼ぶ
        LRESULT lResult = pPicBox->PicBoxProc(hWnd, Message, wParam, lParam);
        //ウィンドウ破壊時にウィンドウプロパティを廃棄する
        if(Message == WM_DESTROY)
            RemoveProp(hWnd, "CPP_CLASS");
        return lResult;
    }
    else
        //ウィンドウズの処理に任せる
        return DefWindowProc(hWnd, Message, wParam, lParam);
}

//
本来のコールバック関数(仮想関数)
LRESULT CALLBACK CPICBOX::PicBoxProc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam) {

    static NMHDR nmhdr;
    switch(Message) {
        //全てのマウス情報を送る
        case WM_LBUTTONDOWN:
        case WM_LBUTTONUP:
        case WM_LBUTTONDBLCLK:
        case WM_MBUTTONDOWN:
        case WM_MBUTTONUP:
        case WM_MBUTTONDBLCLK:
        case WM_RBUTTONDOWN:
        case WM_RBUTTONUP:
        case WM_RBUTTONDBLCLK:
        case WM_MOUSEMOVE:
            nmhdr.hwndFrom = hWnd;
            nmhdr.idFrom = (UINT)wParam;    //マウス情報を送る
            nmhdr.code = Message;
            //nmhdr.codeでマウスメッセージを、
            //nmhdr.idFromでwParamをまま送り
            //座標情報(lParam)はwParam(32bit長)で送る
            SendMessage(m_hParent, WM_NOTIFY, lParam, (LPARAM)&nmhdr);
            break;
        case WM_PAINT:
            PAINTSTRUCT paint;
            OnPaint(BeginPaint(hWnd, &paint));
            EndPaint(hWnd, &paint);
            break;
        default:
            return DefWindowProc(hWnd, Message, wParam, lParam);
    }
    return 0;
}

 

ではWCEditor プロジェクトを構成する各ファイルを見てゆきます。

 

【WCEditor.cpp】

//////////////////////////////////////////
// WCEditor.cpp
//Copyright (c) 05/01/2022 by BCCSkelton
//////////////////////////////////////////
#include    "WCEditor.h"
#include    "WCEditorProc.h"

////////////////
// WinMain関数
////////////////
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPSTR lpCmdLine, int nCmdShow) {

    //2重起動防止
    if(!WCEditor.IsOnlyOne()) {
        HWND hWnd = FindWindow("MainWnd", "WCEditor");
        if(IsIconic(hWnd))
            ShowWindow(hWnd, SW_RESTORE);
        SetForegroundWindow(hWnd);
        return 0L;
    }

    //ウィンドウ登録 - Init(ClassName, hInstance, WndProc, "IDM_MAIN",
    //                        (以下省略可)MAKEINTRESOURCE(IDI_ICON), IDC_ARROW, Brush)
    WCEditor.Init("MainWnd", hInstance, SDIPROC, "IDM_MAIN", MAKEINTRESOURCE(IDI_ICON));

    //ウィンドウ作成と表示-Create(WindowTitle, (以下省略可)Style,
    //                        ExStyle, hParent, hMenu, x, y, w, h)
    if(!WCEditor.Create("WCEditor"))
        return 0L;

    //メッセージループに入る
    return WCEditor.Loop();
}
//(解説:基本的にSkeltonWizardが生成したSDIウインドウ用のコードのままです。Windowsのワイド文字(WCHAR == wchar_t、UTF-16ユニコード)を調べてから知ったのですが、Win2000からエントリーポイントは"wmain"や、"wWinMain"になり、引数がUTF-16になったとのことです。何故かエントリーポイント関数は、他のpostfix型Win32API関数と違い、prefix型になっていますね。勿論ベースのBCCSkeltonの様に、"main"、"WinMain"でcharベースの引数も使えます。)

 

【WCEditor.h】

//////////////////////////////////////////
// WCEditor.h
// Copyright (c) 05/01/2022 by BCCSkelton
//////////////////////////////////////////
//BCCSkeltonのヘッダー-これに必要なヘッダーが入っている
#include    "BCCSkelton.h"
//リソースIDのヘッダー
#include    "ResWCEditor.h"
/*
//ワイド文字を使用する
#include <wchar.h>
//ワイド文字対応StrStrW使用の為
#include    <shlwapi.h>
//ワイド文字対応CWNDWクラス
#include    "CWNDW.h"
//ワイド文字対応CDLGWクラス
#include    "CDLGW.h"
//ワイド文字対応CFONTWクラス
#include    "CFONTW.h"
//ワイド文字対応CCTRLWクラス
#include    "CCTRLW.h"
//ワイド文字対応CSTRWクラス
#include    "CSTRW.h"
//ワイド文字対応CMNDLGWクラス
#include    "CMNDLGW.h"
//ワイド文字対応CSBARWクラス
#include    "CSBARW.h"
*/

//ワイド文字を使用する
#include    "BCCSkeltonWplus.h"

//(解説:コメント/*~*/で囲ったのは、開発時のコードです。その後ワイド文字対応クラスの「アドオン化(BCCSkelotonWplus)」に伴い、"BCCSkelotonWplus.h"にまとめました。)


/////////////////////////////////////////////////////////////////////
//CMyWndクラスをCSDIクラスから派生させ、メッセージ用の関数を宣言する
/////////////////////////////////////////////////////////////////////
class CMyWnd : public CSDI
{
public:    //以下はコールバック関数マクロと関連している
    //2重起動防止用のMutex用ID名称
    CMyWnd(char* UName) : CSDI(UName) {}
    //メンバー変数
    bool m_16OR8 = TRUE;            //UTF-16(TRUE)かUTF-8(FALSE)か
    bool m_BOM = TRUE;                //BOM付きか否か
    int m_POS = 0;                    //変換型式(UTF-16 - 0、BOM付UTF-8 - 1、BOM無UTF-8 - 2)
//(解説:CSTRWの出力形式に合わせてメンバー変数フラグを持たせました。)

    //メニュー項目、ダイアログコントロール関連
    bool OnNew();
    bool OnOpen();
    bool OnSave();
    bool OnSaveas();
    bool OnExit();
    bool OnCut();
    bool OnCopy();
    bool OnPaste();
    bool OnUtf16();
    bool OnUtf8();
    bool OnUtf8n();
    bool OnVersion();

//(解説:全てメニューアイテムです。)

    //ウィンドウメッセージ関連
    bool OnCreate(WPARAM, LPARAM);
    bool OnSize(WPARAM, LPARAM);
    bool OnClose(WPARAM, LPARAM);
    //ユーザー定義関数
    void ChangeCheck(int);

//(解説:メニューにチェックを付ける関数です。)

};

////////////////////////////////////////////////////////////////////////
//派生させたCMyWndクラスのインスタンスとコールバック関数(マクロ)の作成
//主ウィンドウはダイアログと違い、コールバック関数は一つしか作れない
////////////////////////////////////////////////////////////////////////
CMyWnd WCEditor("WCEditor");    //ウィンドウクラスインスタンスの生成

BEGIN_SDIMSG(WCEditor)    //ダイアログと違い、コールバック関数名を特定しない
    //メニュー項目、ダイアログコントロール関連
    ON_COMMAND(WCEditor, IDM_NEW, OnNew())
    ON_COMMAND(WCEditor, IDM_OPEN, OnOpen())
    ON_COMMAND(WCEditor, IDM_SAVE, OnSave())
    ON_COMMAND(WCEditor, IDM_SAVEAS, OnSaveas())
    ON_COMMAND(WCEditor, IDM_EXIT, OnExit())
    ON_COMMAND(WCEditor, IDM_CUT, OnCut())
    ON_COMMAND(WCEditor, IDM_COPY, OnCopy())
    ON_COMMAND(WCEditor, IDM_PASTE, OnPaste())
    ON_COMMAND(WCEditor, IDM_UTF16, OnUtf16())
    ON_COMMAND(WCEditor, IDM_UTF8, OnUtf8())
    ON_COMMAND(WCEditor, IDM_UTF8N, OnUtf8n())
    ON_COMMAND(WCEditor, IDM_VERSION, OnVersion())
    //ウィンドウメッセージ関連
    ON_CREATE(WCEditor)
    ON_SIZE(WCEditor)
    ON_CLOSE(WCEditor)
END_WNDMSG

//////////////////////
//UTF Types Constants
//////////////////////
const WCHAR *g_UTFTypes[3] = {L"UTF-16(BOM付)", L"UTF-8(BOM付)", L"UTF-8(BOM無)"};

//(解説:メニューアイテム番号の0、1、2が表示用のワイド文字列に対応するように設定しています。)


///////////////////////
//ステータスバーの作成
///////////////////////
CSBARW SBar;

////////////////////////
//EDIT BOX用コントロール
////////////////////////
CCTRLW g_Edit;

////////////////////////
//コモンダイアログの作成
////////////////////////
CMNDLGW g_Cmndlg;

///////////////////
//ファイル関係変数
///////////////////
CSTRW g_FileName;
CSTRW g_File;

//(解説:今回開発したワイド文字対応クラスを使っています。)


///////////////////////////////////////////
// CDLGクラスからVERSIONDLGクラスを派生
// 複数の同一ダイアログ変数とダイアログも作
// れるが、一つのダイアログに一つの派生ダイ
// アログクラスを作成するのが基本
///////////////////////////////////////////
class VERSIONDLG : public CDLG {
public:
    bool OnInit(WPARAM, LPARAM);
    bool OnIdok();
};


////////////////////////////////////////////////////////////////////////////
// VERSIONDLGクラスダイアログ変数の生成とそのコールバック関数(マクロ)を定義
// 複数同一クラスのダイアログを作成することを予期してコールバック関数を明記
////////////////////////////////////////////////////////////////////////////
VERSIONDLG versiondlg;

BEGIN_MODALDLGMSG(versiondlgProc, versiondlg)    //第1引数がコールバック関数の名前
    ON_COMMAND(versiondlg, IDOK, OnIdok())
END_DLGMSG

 

【WCEditorProc.h】

//////////////////////////////////////////
// WCEditorProc.h
// Copyright (c) 05/01/2022 by BCCSkelton
//////////////////////////////////////////

/////////////////////////////////
//主ウィンドウCMyWndの関数の定義
//ウィンドウメッセージ関数
/////////////////////////////////
bool CMyWnd::OnCreate(WPARAM wParam, LPARAM lParam) {

    //コモンコントロールの初期化
    InitCommonControls();

//(解説:以下ステータスバーはワイド文字対応型ですので、L付文字列を使っています。)

    //ステータスバー登録-Init(hWnd, hIinstance, ID, (以下省略可)Style)
    SBar.Init(m_hWnd, m_hInstance, STATUSBAR);
    //ステータスバー区画設定
    int sec[3] = {100, 200, -1};
    SBar.SetSection(3, sec);
    //ステータスバー文字列設定
    SBar.SetText(0, L"WCEditor Ver 1.0");
    SBar.SetText(1, g_UTFTypes[0]);
    SBar.SetText(2, L"新規ファイル");
    //EDITコントロールの作成(登録、作成、)
    g_Edit.Init(L"EDIT", m_hInstance);
    g_Edit.Create(L"", WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_HSCROLL | WS_VSCROLL |
                    ES_MULTILINE | ES_WANTRETURN | ES_AUTOHSCROLL | ES_AUTOVSCROLL | ES_LEFT,
                    WS_EX_CLIENTEDGE, m_hWnd, IDC_EDIT, 0, 0, 0, 0);
    SendItemMsg(IDC_EDIT, EM_SETLIMITTEXT, 1048576, 0);        //1M bytes

//(解説:CCTRLWクラスでエディットボックスを作りますが、これもワイド文字対応です。)

    return TRUE;
}

 

//(解説:以下も同様にL付文字列や、W付きワイド文字対応関数を使っています。)

bool CMyWnd::OnSize(WPARAM wParam, LPARAM lParam) {

    //ステータスバー自動調整
    SBar.AutoSize();
    //EDITコントロール位置調整
    RECT rec;
    GetWindowRect(SBar.GetHandle(), &rec);
    MoveWindow(GetDlgItem(m_hWnd, IDC_EDIT), 0, 0, LOWORD(lParam), HIWORD(lParam) - rec.bottom + rec.top, TRUE);
    return TRUE;
}

bool CMyWnd::OnClose(WPARAM wParam, LPARAM lParam) {

    if(MessageBoxW(m_hWnd, L"終了しますか", L"終了確認",
                    MB_YESNO | MB_ICONINFORMATION) == IDYES)

//(解説:ワイド文字対応のW仕様ですね。)

        //処理をするとDestroyWindow、PostQuitMessageが呼ばれる
        return TRUE;
    else
        //そうでなければウィンドウではDefWindowProc関数をreturn、ダイアログではreturn FALSEとなる。
        return FALSE;
}

/////////////////////////////////
//主ウィンドウCMyWndの関数の定義
//メニュー項目、コントロール関数
/////////////////////////////////
bool CMyWnd::OnNew() {

    //IDC_EDITに文字列があれば保存するか確認するする
    if(SendItemMsg(IDC_EDIT, WM_GETTEXTLENGTH, 0, 0)) {
        if(MessageBoxW(m_hWnd, L"現在のテキストを保存しますか?", L"確認", MB_YESNO | MB_ICONQUESTION) == IDYES)
            OnSave();
    }
    //IDC_EDITを初期化
    SendItemMsg(IDC_EDIT, WM_SETTEXT, 0, (LPARAM)"");
    //ステータスバーに新規であることを表示
    SBar.SetText(2, L"新規ファイル");
    g_FileName = L"";
    return TRUE;
}

bool CMyWnd::OnOpen() {

    //開くファイルを指定する
    WCHAR* fn = g_Cmndlg.GetFileName(m_hWnd, L"テキストファイル(*.txt)\0*.txt\0全てのファイル\0*.*\0\0", TRUE);
    //ファイルパス、名をg_FileNameに保存する
    g_FileName = fn;
    //g_Fileにファイルを読み込む
    g_File.FromFile(g_FileName.ToChar());

//(解説:これがワイド文字対応ファイルの読み込みです。ASCII版と全く変わらないコーディングです。)

    //IDC_EDITにファイルを表示
    SendDlgItemMessageW(m_hWnd, IDC_EDIT, WM_SETTEXT, 0, (LPARAM)g_File.ToChar());
    //ステータスバーにファイルパス名を表示
    SBar.SetText(2, g_FileName.ToChar());
    return TRUE;
}

bool CMyWnd::OnSave() {

    if(!*g_FileName.ToChar())    //新規ファイルならOnSaveasへ移行
        OnSaveas();
    else {
        //WCHAR配列を文字列長分確保してIDC_EDITの文字列を取得する
        DWORD size = SendItemMsg(IDC_EDIT, WM_GETTEXTLENGTH, 0, 0) + 1;
        WCHAR* ptr = new WCHAR[size];
        //IDC_EDITのデータをg_Fileへ保存
        SendDlgItemMessageW(m_hWnd, IDC_EDIT, WM_GETTEXT, lstrlenW(ptr) + 1, (LPARAM)ptr);
        //データをg_Fileに保存してWCHAR配列は破棄する
        g_File = ptr;
        delete [] ptr;

//(解説:以下がフラグに従って、3様のUTF型式とBOMの有無でファイルを保存するところです。)

        //UTF区分、BOMの有無に従って保存
        if(m_16OR8)        //UTF-16
            g_File.ToFile(g_FileName.ToChar());
        else {            //UTF-8
            if(m_BOM)    //BOM付
                g_File.ToUTF8File(g_FileName.ToChar(), TRUE);
            else        //BOM無
                g_File.ToUTF8File(g_FileName.ToChar(), FALSE);
        }
    }
    return TRUE;
}

bool CMyWnd::OnSaveas() {

    //保存するファイルパス名を指定する
    WCHAR* fn = g_Cmndlg.GetFileName(m_hWnd, L"テキストファイル(*.txt)\0*.txt\0全てのファイル\0*.*\0\0", FALSE);
    g_FileName = fn;
    //WCHAR配列を文字列長分確保してIDC_EDITの文字列を取得する
    DWORD size = SendItemMsg(IDC_EDIT, WM_GETTEXTLENGTH, 0, 0) + 1;
    WCHAR* ptr = new WCHAR[size];
    //IDC_EDITのデータをg_Fileへ保存
    SendDlgItemMessageW(m_hWnd, IDC_EDIT, WM_GETTEXT, lstrlenW(ptr) + 1, (LPARAM)ptr);
    //データをg_Fileに保存してWCHAR配列は破棄する
    g_File = ptr;
    delete [] ptr;

//(解説:ここも同様に、3様のUTF型式とBOMの有無でファイルを保存するところです。)

    //UTF区分、BOMの有無に従って保存
    if(m_16OR8)        //UTF-16
        g_File.ToFile(g_FileName.ToChar());
    else {            //UTF-8
        if(m_BOM)    //BOM付
            g_File.ToUTF8File(g_FileName.ToChar(), TRUE);
        else        //BOM無
            g_File.ToUTF8File(g_FileName.ToChar(), FALSE);
    }
    //ステータスバーにファイルパス名を表示
    SBar.SetText(2, g_FileName.ToChar());
    return TRUE;
}

bool CMyWnd::OnExit() {

    SendMsg(WM_CLOSE, 0, 0);    //終了処理
    return TRUE;
}

bool CMyWnd::OnCut() {

    SendItemMsg(IDC_EDIT, WM_CUT, 0, 0);    //Cut処理
    return TRUE;
}

bool CMyWnd::OnCopy() {

    SendItemMsg(IDC_EDIT, WM_COPY, 0, 0);    //Copy処理
    return TRUE;
}

bool CMyWnd::OnPaste() {

    SendItemMsg(IDC_EDIT, WM_PASTE, 0, 0);    //Paste処理
    return TRUE;
}

 

//(解説:以下がメニューアイテムによるフラグの設定です。)

bool CMyWnd::OnUtf16() {

    m_16OR8 = TRUE;        //UTF-16(TRUE)
    m_BOM = TRUE;        //BOM付
    ChangeCheck(0);        //メニューアイテムにチェックを入れる
    SBar.SetText(1, g_UTFTypes[0]);
    return TRUE;
}

bool CMyWnd::OnUtf8() {

    m_16OR8 = FALSE;    //UTF-8(FALSE)
    m_BOM = TRUE;        //BOM付
    ChangeCheck(1);        //メニューアイテムにチェックを入れる
    SBar.SetText(1, g_UTFTypes[1]);
    return TRUE;
}

bool CMyWnd::OnUtf8n() {

    m_16OR8 = FALSE;    //UTF-8(FALSE)
    m_BOM = FALSE;        //BOM無
    ChangeCheck(2);        //メニューアイテムにチェックを入れる
    SBar.SetText(1, g_UTFTypes[2]);
    return TRUE;
}

bool CMyWnd::OnVersion() {

    //IDD_VERSIONダイアログを呼び出す
    versiondlg.DoModal(m_hWnd, "IDD_VERSION", versiondlgProc, m_hInstance);
    return TRUE;
}

///////////////////////////////////
//ユーザー定義関数
//メニューアイテムチェック切替
///////////////////////////////////
void CMyWnd::ChangeCheck(int pos) {

    HMENU hMenu = GetSubMenu(GetMenu(m_hWnd), 2);    //表示メニューハンドルの取得
    //メニューアイテム
    MENUITEMINFO mii;
    mii.cbSize = sizeof(mii);
    mii.fMask = MIIM_STATE;
    //既にチェックされているアイテムのチェックを外す
    mii.fState = MFS_UNCHECKED;
    SetMenuItemInfo(hMenu, m_POS, TRUE, &mii);
    //指定アイテムをチェックする
    mii.fState = MFS_CHECKED;
    SetMenuItemInfo(hMenu, pos, TRUE, &mii);
    //チェックアイテムの記録
    m_POS = pos;
}

//(解説:3様のUTF型式とBOMの有無に基づいてメニューアイテムにチェックを入れます。)


///////////////////////////////
//ユーザーダイアログの関数定義
//コントロール関数
///////////////////////////////
bool VERSIONDLG::OnInit(WPARAM wParam, LPARAM lParam) {

    WCHAR* str = L"WCEditor Version 1.0\nCopyright 2022 by Ysama";
    SendDlgItemMessageW(m_hWnd, IDC_LABEL, WM_SETTEXT, 0, (LPARAM)str);
    return TRUE;
}

//(解説:バージョンダイアログの文字をワイド文字にしています。)


bool VERSIONDLG::OnIdok() {

    EndModal(TRUE);
    return TRUE;
}

 

まぁ、こんなところでしょうか?

このようにUTF-16のワイド文字対応しても、ユーザーに得るところが乏しく、単にプログラムが大きくなり(理屈上)処理負荷が大きくなるだけ、という感が残ります。(そこらへんがUTF-8が好まれる所以ですね。)

↑のプログラムでもCWNDWやCDLGWは使っておらず、「完全なワイド文字対応」ではないですが、それは「データ文字列を扱っていないのに、(例えばCCTRLクラスの"EDIT"のような)何故アルファベットのウィンドウクラス名を1バイト文字列から2バイト文字にしなくてはならないのか?」という疑念が生じ、必然性に疑問が付いたからです。それが「アドオン化」にしようと思った理由でもあります。

 

今回のワイド文字化対応はAlbum開発中に遭遇した「バグ探しと(1バイト文字では対応できない)バグ退治ツールの為」という大義があった為、その目的に必要なワイド文字対応クラスやワイド文字対応プログラムをBCCSkeltonでが書けることを示しただけであり、今後はやはり(ワイド文字を使う必然性が無く、可能なら)ASCII版に戻ってコンパクトに行きたいですね。

 

お知らせ:前のブログでBCCSkeltonのワイド文字対応を"BCCSkeltonWCV (Widew Characters Version)"としていましたが、何も全て馬鹿みたいにワイド文字化する必要はなく、「ASCII文字版とワイド文字版を最適化併用した方が賢いな」ということで、従来のASCII版を"BCCSkelton"のままとし、ワイド文字を扱う必要のあるクラスは'W'を付けたクラスを設けて、"BCCSkeltonWplus"(注)として「アドオン」型の対応を取ることとしました。今後も必要に応じて新たなWクラスを作成し、追加して行きます。

注:BCCForm and BCCSkeltonのシステムフォールダーに"BCCSkeltonWplus"フォールダーを設け、その中にワイド文字対応クラスのヘッダーを保存します。またそれらは"BCCSkeltonWplus.h"をインクルードして呼び出すことにします。

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

ひょんなこと(注)から、ずーっと避けてきたユニコード、ワイド文字に頭を突っ込んできましたが、今回からBCCSkeltonにおけるワイド文字を使った具体的なプログラミングを見てゆきます。

注:「バグは続くよ、どこまでも」シリーズの「禁則文字混入事件」編を参照。(リンク先は最初の記事)

 

いつも通り、「BCCSkeltonのプログラム作法はリソース作りから」です。今回は最も単純なエディターを作るので「SDIウィンドウにエディットコントロールを一個張り付けるだけ」ですが、一応メニューとバージョンダイアログは用意します。

 

で、(既にWCEditorを完成して)今日からプログラミングぐログを書こうとして「ん?まてよ?」と思い当たりました。

リソースコンパイラー(brc32.exe)はワイド文字に対応しているのかしら?

というのが疑問です。

いつものようにググって調べますが、(面倒見の悪いEmbarcaderoのことで)すぐに答えとなる情報はありません。ならば、といつも通り実験を行います。その結果は、...以下の//(解説:)を参照してください。

 

【WCEditor.rc】(ResWCEditor.hは省略します。)

//-----------------------------------------
//             BCCForm Ver 2.41
//    An Easy Resource Editor for BCC
//  Copyright (c) February 2002 by ysama
//-----------------------------------------
#include    "ResWCEditor.h"
//(解説:先ずファイル系ですが、「L"(ファイルパス、名)"」とすると文字列を認識はしますが、読み込めずエラーとなりますね。まぁ、ファイルパス名はASCIIでもよいのですが、「L"」で始まる文字列も読み込んでエラーにしなくてもよいように思えますが。)


//----------------------------------
// ダイアログ (IDD_VERSION)
//----------------------------------
IDD_VERSION DIALOG DISCARDABLE 0, 0, 160, 40
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | DS_MODALFRAME | DS_3DLOOK | DS_CENTER
CAPTION L"バージョン情報"
FONT 9, L"Times New Roman"
{
 CONTROL IDI_ICON, 0, L"STATIC", SS_SUNKEN | SS_ICON | WS_CHILD | WS_VISIBLE, 12, 10, 32, 32
 CONTROL L"", IDC_LABEL, L"STATIC", SS_CENTER | SS_SUNKEN | WS_CHILD | WS_VISIBLE, 42, 8, 80, 24
 CONTROL L"OK", IDOK, L"BUTTON", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 130, 14, 20, 12
}

//(解説:ダイアログは完全にワイド文字対応しています。ダンプを取って調べてはいませんが、L""文字列を2バイトコードにしているのは間違いないでしょう。)

 

//-------------------------
// メニュー(IDM_MAIN)
//-------------------------
IDM_MAIN MENU DISCARDABLE
{
    POPUP L"ファイル(&F)"
    {
        MENUITEM L"新規(&N)", IDM_NEW
        MENUITEM L"データを開く(&O)", IDM_OPEN
        MENUITEM L"データの保存(&S)", IDM_SAVE
        MENUITEM L"名前をつけて保存(&A)", IDM_SAVEAS
        MENUITEM SEPARATOR
        MENUITEM L"終了(&X)", IDM_EXIT
    }
    POPUP L"編集(&E)"
    {
        MENUITEM L"切り取り(&T)", IDM_CUT
        MENUITEM L"コピー(&C)", IDM_COPY
        MENUITEM L"貼り付け(&P)", IDM_PASTE
    }
    POPUP L"変換型式(&F)"
    {
        MENUITEM L"UTF-16(BOM付)", IDM_UTF16, CHECKED
        MENUITEM L"UTF-8(BOM付)", IDM_UTF8
        MENUITEM L"UTF-8(BOM無)", IDM_UTF8N
    }
    POPUP L"ヘルプ(&H)"
    {
        MENUITEM L"バージョン情報(&V)", IDM_VERSION
    }
}

//(解説:メニューも完全にワイド文字対応しています。)


//--------------------------
// イメージ(IDI_ICON)
//--------------------------
IDI_ICON    ICON    DISCARDABLE    "C:\Users\(パス)\WCEditor\Icon.ico"

//(解説:これもファイル系なのでワイド文字対応していません。エラーになります。)

 

ということで、brc32.exeもリソース自体についてはワイド文字対応がされている、といってよいのでしょう。(実はバージョンダイアログでバージョン名がASCIIになるからと故意とブランクにしたのですが、不要だったようです。)

 

ps. brc32のワイド文字対応を調べていたら、こんなサイトを見つけました。16年前の環境がよく分かり、懐かしいです。

Borland C++ Compiler 5.5 - FAQ

 

追記:愕きモモノキ、なんですが、今朝このページを見てリンクを貼りましたが、その後見直してみると"Service unavailable"で表示されません。何故なんだ、Embarcadero?

どーせ暇だから、ということで、矢張りCSTRWクラスのチェックを兼ねて、「Win32 + C++プログラミング」定番のメモ帳のようなエディターを作ってみました。まぁ、文字列を入力、表示してファイル入出力するだけのプログラムなのでBCCSkeltonを使えば本当にチャチャッとできますが、それでは面白くないので「BCCSkeltonは使うけど、1バイト文字は見える限り使わない"char"縛り」を入れてプログラミングしてみました。またその際にBCCSkeltonクラスで使うものは皆ワイド文字化してみよう、ということで、既にFileNameCleanerで作成した

CSTRW.h

CDLGW.h

に加え、今回

CWNDW.h

CFONTW.h

CDLGW.h

CCTRLW.h

CSBARW.h

を作ってみました。

 

その結果、こんなワイド文字エディター(Wide Characters EditorーWCEditor)ができました。

 

扱えるUTFは、

Windows御用達のUTF-16はBOMが必然、WEB御用達で現在風靡しているUTF-8はBOMありとBOMなしがあるというCSTRWの仕様通りです。

 

上記のように、この3つのUTFパターンで色々と読み書きしましたが、一応正常にできるようです。そのついでに前に作ったASCIIベースのRTEditor(Rich Text Editor)でUTF-16、UTF-8ボム付、UTF-8ボム無を読んでみると...

一番上がUTF-16で文字化けは当然、次がUTF-8ボム無でこれもASCII文字以外は文字化けですが、なんと(!)UTF-8のボム付はASCIIエディターでそのまま読めることが分かりました。(これだからUTF-8が一世風靡するんですね。)

 

未だ次のネタが仕入れ前なので、駄文を書きながらこのワイド文字化BCCSkeltonを使ったWCEditorの解説でもしてゆきましょう。

 

前回書いたように、(Windowsは内部的にUnicodeのエンコーダーをUTF-16のワイド文字にしていますが)巷ではUTF-8が主流となっているらしく、それもWEB系の方々には「BOM無しUTF-8」が必要なのだと。一方Microsoftも既にWin32API(windows.h)にUTF-8も扱えるWideCharToMultiByte とMultiByteToWideCharという便利な関数を持っているので、これは使うしかないな、と思い、少なくともUTF-16(BOM付)の他にUTF-8の読み書きを用意しようと考えました。しかし、UTF-8の読み書きはBOMの有り無しの2種類を用意しないとなりません。

 

以下に修正部分のみ色付きで紹介します。

 

【改訂版CSTRW.h】

//-------------------------
//         CSTRW.h
//   Copyright April 2022
//     By K. Yoshida
//      Version 1.0A
//注:使用にはwchar.hが必要
//-------------------------
#ifndef    _CSTRW_
#define _CSTRW_

class CSTRW {

    WCHAR*    m_str;

public:
    CSTRW();                                    //コンストラクター(無)
    CSTRW(const WCHAR*);                        //コンストラクター(文字列)
    CSTRW(const CSTRW&);                        //コンストラクター(CSTRW)
    CSTRW(const long);                            //コンストラクター(整数)
    ~CSTRW();                                    //デストラクター
    CSTRW& operator = (const CSTRW&);            //operator = (B) ---> = B;
    CSTRW& operator = (const long);                //operator = (B) ---> = B;
    WCHAR* ToChar() {return m_str;}                //文字列ポインターとして返す
    void Print();                                //m_strを印字する
    friend CSTRW operator + (const CSTRW&, const CSTRW&);    //A operator + (B)  --->= A + B;
    friend bool operator == (const CSTRW&, const CSTRW&);    //A operator == (B) --->= A == B;
    friend bool operator == (const CSTRW&, const WCHAR*);    //A operator == (B) --->= A == B;
    friend bool operator != (const CSTRW&, const CSTRW&);    //A operator == (B) --->= A != B;
    friend bool operator != (const CSTRW&, const WCHAR*);    //A operator == (B) --->= A != B;
    bool FromFile(WCHAR*, WCHAR*, HWND);        //ファイルからの読込
    bool FromFile(WCHAR*);                        //ファイルからの読込
    bool ToFile(WCHAR*, WCHAR*, HWND);            //ファイルへの書き込み
    bool ToFile(WCHAR*);                        //ファイルへの書き込み
    bool ToUTF8File(WCHAR*, bool);                //ファイルへの書き込み(boolはBOM有無のフラグ)
//(解説:UTF-8の書き込みはBOM有無指定などもあり、別関数にしました。)

    bool Next(CSTRW&);                            //m_strの単語を切り取り出す
    bool GetLine(CSTRW&);                        //この行末まで進む
    bool NextLine();                            //次の行頭まで進む
    WCHAR* Find(WCHAR*, WCHAR*);                //文字列の検索
};

//コンストラクター(無)
CSTRW::CSTRW() {

    m_str = new WCHAR[1];
    *m_str = L'\0';
}

//コンストラクター(文字列)
CSTRW::CSTRW(const WCHAR* string) {

    m_str = new WCHAR[lstrlenW(string) + 1];
    lstrcpyW(m_str, string);
}

//コンストラクター(CSTRW)
CSTRW::CSTRW(const CSTRW& cst) {

    m_str = new WCHAR[lstrlenW(cst.m_str) + 1];
    lstrcpyW(m_str, cst.m_str);
}

//コンストラクター(整数)
CSTRW::CSTRW(const long i) {

    WCHAR sbuff[MAX_PATH / 8];
    wsprintfW(sbuff, L"%d", i);
    m_str = new WCHAR[lstrlenW(sbuff) + 1];
    lstrcpyW(m_str, sbuff);
}

//デストラクター
CSTRW::~CSTRW() {

    delete [] m_str;
}

//CSTRWの代入
CSTRW& CSTRW::operator = (const CSTRW& cst) {

    if(&cst != this) {    //You can't copy yourself
        delete [] m_str;
        m_str = new WCHAR[lstrlenW(cst.m_str) + 1];
        lstrcpyW(m_str, cst.m_str);
    }
    return *this;
}

//整数の代入
CSTRW& CSTRW::operator = (const long i) {

    WCHAR sbuff[MAX_PATH / 8];
    wsprintfW(sbuff, L"%d", i);
    m_str = new WCHAR[lstrlenW(sbuff) + 1];
    lstrcpyW(m_str, sbuff);
    return *this;
}

//m_strを印字する
void CSTRW::Print() {

    MessageBoxW(NULL, m_str, L"CSTRW Message", MB_OK);
}

//追加演算子の定義
CSTRW operator + (const CSTRW& cst1, const CSTRW& cst2) {

    CSTRW strw;
    delete [] strw.m_str;
    strw.m_str = new WCHAR[lstrlenW(cst1.m_str) + lstrlenW(cst2.m_str) + 1];
    lstrcpyW(strw.m_str, cst1.m_str);
    lstrcatW(strw.m_str, cst2.m_str);
    return strw;
}

//比較演算子の定義(1)
bool operator == (const CSTRW& cst1, const CSTRW& cst2) {

    return !lstrcmpW(cst1.m_str, cst2.m_str);
}

//比較演算子の定義(2)
bool operator == (const CSTRW& cst, const WCHAR* str) {

    return !lstrcmpW(cst.m_str, str);
}

//比較演算子の定義(3)
bool operator != (const CSTRW& cst1, const CSTRW& cst2) {

    return lstrcmpW(cst1.m_str, cst2.m_str);
}

//比較演算子の定義(4)
bool operator != (const CSTRW& cst, const WCHAR* str) {

    return lstrcmpW(cst.m_str, str);
}

//ファイルからの読込
bool CSTRW::FromFile(WCHAR* Filter, WCHAR* FilePath, HWND hWnd = NULL) {

    WCHAR FileName[MAX_PATH];
    WCHAR file_path[MAX_PATH];
    lstrcpyW(file_path, FilePath);    //Default Path
    OPENFILENAMEW ofn;                //オープンファイルダイアログ構造体
    //オープンファイルダイアログでファイル名を取得する
    FileName[0] = NULL;
    ZeroMemory(&ofn, sizeof(ofn));    //ゼロ初期化
    ofn.lStructSize        = sizeof(ofn);
    ofn.hwndOwner        = hWnd;
    ofn.lpstrFilter        = Filter;
    ofn.nFilterIndex    = 1;
    ofn.lpstrFile        = FileName;
    ofn.nMaxFile        = MAX_PATH;
    if(!*FilePath)                    //規定値のNULLであれば現在のディレクトリー
        GetCurrentDirectoryW(MAX_PATH, file_path);
    else
        lstrcpyW(file_path, FilePath);
    ofn.lpstrInitialDir = file_path;
    ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
    if(!GetOpenFileNameW(&ofn))    return FALSE;        //読み込み結果
    //ファイルを読み込む
    BOOL bSuccess = FALSE;
    HANDLE hFile;
    hFile = CreateFileW(FileName, GENERIC_READ, FILE_SHARE_READ, NULL,
                        OPEN_EXISTING, NULL, NULL);    //ファイルを読込みでオープン
    if(hFile != INVALID_HANDLE_VALUE) {                //オープンできたか
        DWORD dwFileSize = GetFileSize(hFile, NULL);
        if(dwFileSize != 0xFFFFFFFF) {                //-1は(0xFFFFFFFF)エラー
            LPBYTE Buff;                            //読み込みバッファ用ポインター
            Buff = new BYTE[dwFileSize];
            DWORD dwRead;
            if(ReadFile(hFile, Buff, dwFileSize, &dwRead, NULL)) {
                bSuccess = TRUE; //読み込み成功
            }

            if(Buff[0] == 0xFF && Buff[1] == 0xFE) {//UTF-16、UTF-32のBOMチェック
                if(Buff[2] | Buff[3]) {                //いずれもNULLではない→UTF-16のBOM
                    delete [] m_str;                //元の文字列を廃棄して
                    //BOM以降の読み込んだデータを入れられるようにする
                    m_str = new WCHAR[dwFileSize / sizeof(WCHAR)];
                    CopyMemory(m_str, Buff + 2, dwFileSize - 2);
                    //念のため、バッファの終端にNULLを置く
                    m_str[(dwFileSize - 2) / sizeof(WCHAR) - 1] = L'\0';
                }
                else {    //Buff[2]とBuff[3])がいずれもNULL→UTF-32のBOM(またはUTF-16でNULLのみのファイルの場合)
                    bSuccess = FALSE;    //UTF-32BOMの場合、何もせず読み込み失敗扱い
                }

//(解説:UTF-32の場合、FALSEを返すことにしました。)

            }
            else if(Buff[0] == 0xEF && Buff[1] == 0xBB && Buff[2] == 0xBF) {    //UTF-8のBOMチェック
                int iBuffSize = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, (LPCCH)(Buff + 3), -1, NULL, NULL);
                if(iBuffSize) {    //UTF-8文字列の場合
                    //m_str用メモリーの確保
                    if(m_str)
                        delete [] m_str;
                    m_str = new WCHAR[iBuffSize];
                    //UTF8をWCHARへ変換
                    MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, (LPCCH)(Buff + 3), -1, m_str, iBuffSize);
                }
                else {                    //BOM付きUTF-8で変換不能な場合
                    bSuccess = FALSE;    //何もせず読み込み失敗扱い
                }

//(解説:これはBOM付の処理です。読み込んだデータの頭にBOMがあるかチェックします。またMultiByteToWideChar関数は変換先をNULLにして「素振り」すると必要な文字数を返してくれるので便利です。その為、二度呼ぶ必要があります。)

            }
            else {                        //UTF-16、32、8のBOMがない場合
                //先ずBOM無しUTF-8か否かをチェック
                int iBuffSize = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, (LPCCH)(Buff), -1, NULL, NULL);
                if(iBuffSize) {            //BOM無しUTF-8の場合
                    //m_str用メモリーの確保
                    if(m_str)
                        delete [] m_str;
                    m_str = new WCHAR[iBuffSize];
                    //UTF8をWCHARへ変換
                    MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, (LPCCH)(Buff), -1, m_str, iBuffSize);
                }

  //(解説:これはBOM無しの処理です。「丸のみ」の前に一応UTF-8で読み込めるか否かトライする形にしました。)

             else {                    //BOMが無く、UTF-8変換できない場合「丸のみ」
                    m_str = new WCHAR[dwFileSize / sizeof(WCHAR) + 1];
                    CopyMemory(m_str, Buff, dwFileSize);
                    //念のため、バッファの終端にNULLを置く
                    m_str[dwFileSize / sizeof(WCHAR)] = L'\0';
                }
            }
            delete [] Buff;                            //読み込みバッファ解放
        }
        CloseHandle(hFile);                            //ファイルのクローズ
    }
    return bSuccess;
}

//ファイルからの読込
bool CSTRW::FromFile(WCHAR* FileName) {

    BOOL bSuccess = FALSE;
    HANDLE hFile;
    hFile = CreateFileW(FileName, GENERIC_READ, FILE_SHARE_READ, NULL,
                        OPEN_EXISTING, NULL, NULL);    //ファイルを読込みでオープン
    if(hFile != INVALID_HANDLE_VALUE) {                //オープンできたか
        DWORD dwFileSize = GetFileSize(hFile, NULL);
        if(dwFileSize != 0xFFFFFFFF) {                //-1は(0xFFFFFFFF)エラー
            LPBYTE Buff;                            //読み込みバッファ用ポインター
            Buff = new BYTE[dwFileSize];
            DWORD dwRead;
            if(ReadFile(hFile, Buff, dwFileSize, &dwRead, NULL)) {
                bSuccess = TRUE; //読み込み成功
            }
            if(Buff[0] == 0xFF && Buff[1] == 0xFE) {//UTF-16、UTF-32のBOMチェック
                if(Buff[2] | Buff[3]) {                //いずれもNULLではない→UTF-16のBOM
                    delete [] m_str;                //元の文字列を廃棄して
                    //BOM以降の読み込んだデータを入れられるようにする
                    m_str = new WCHAR[dwFileSize / sizeof(WCHAR)];
                    CopyMemory(m_str, Buff + 2, dwFileSize - 2);
                    //念のため、バッファの終端にNULLを置く
                    m_str[(dwFileSize - 2) / sizeof(WCHAR) - 1] = L'\0';
                }
                else {    //Buff[2]とBuff[3])がいずれもNULL→UTF-32のBOM(またはUTF-16でNULLのみのファイルの場合)
                    bSuccess = FALSE;    //UTF-32BOMの場合、何もせず読み込み失敗扱い
                }

            }
            else if(Buff[0] == 0xEF && Buff[1] == 0xBB && Buff[2] == 0xBF) {    //UTF-8のBOMチェック
                int iBuffSize = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, (LPCCH)(Buff + 3), -1, NULL, NULL);
                if(iBuffSize) {    //UTF-8文字列の場合
                    //m_str用メモリーの確保
                    if(m_str)
                        delete [] m_str;
                    m_str = new WCHAR[iBuffSize];
                    //UTF8をWCHARへ変換
                    MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, (LPCCH)(Buff + 3), -1, m_str, iBuffSize);
                }
                else {                    //BOM付きUTF-8で変換不能な場合
                    bSuccess = FALSE;    //何もせず読み込み失敗扱い
                }

            }
            else {                        //UTF-16、32、8のBOMがない場合
                //先ずBOM無しUTF-8か否かをチェック
                int iBuffSize = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, (LPCCH)(Buff), -1, NULL, NULL);
                if(iBuffSize) {            //BOM無しUTF-8の場合
                    //m_str用メモリーの確保
                    if(m_str)
                        delete [] m_str;
                    m_str = new WCHAR[iBuffSize];
                    //UTF8をWCHARへ変換
                    MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, (LPCCH)(Buff), -1, m_str, iBuffSize);
                }

                else {                    //BOMが無く、UTF-8変換できない場合「丸のみ」
                    m_str = new WCHAR[dwFileSize / sizeof(WCHAR) + 1];
                    CopyMemory(m_str, Buff, dwFileSize);
                    //念のため、バッファの終端にNULLを置く
                    m_str[dwFileSize / sizeof(WCHAR)] = L'\0';
                }
            }
            delete [] Buff;                            //読み込みバッファ解放
        }
        CloseHandle(hFile);                            //ファイルのクローズ
    }
    return bSuccess;
}

//(解説:同じ処理です。)


//ファイルへの書き込み(規定値UTF-16 リトルエンディアン)
bool CSTRW::ToFile(WCHAR* Filter, WCHAR* DefExt, HWND hWnd = NULL) {

    WCHAR FileName[MAX_PATH];
    WCHAR file_path[MAX_PATH];
    OPENFILENAMEW ofn;                //オープンファイルダイアログ構造体
    //オープンファイルダイアログでファイル名を取得する
    ZeroMemory(&ofn, sizeof(ofn));    //ゼロ初期化
    FileName[0] = NULL;
    ofn.lStructSize        = sizeof(ofn);
    ofn.hwndOwner        = hWnd;
    ofn.lpstrFilter        = Filter;
    ofn.nFilterIndex    = 1;
    ofn.lpstrFile        = FileName;
    ofn.nMaxFile        = MAX_PATH;
    ofn.lpstrInitialDir = L".";
    ofn.lpstrDefExt        = DefExt;    //デフォルトの拡張子
    ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY |
                OFN_OVERWRITEPROMPT;
    if(!GetSaveFileNameW(&ofn))    return FALSE;;
    //ファイルを書き込む
    BOOL bSuccess = FALSE;
    HANDLE hFile;
    hFile = CreateFileW(FileName, GENERIC_WRITE, NULL, NULL,    //ファイルを書込みでオープン
                        CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if(hFile != INVALID_HANDLE_VALUE) {        //オープンできたか
        //バッファから文字列長取得し、NULL終端も加算
        DWORD dwTextLength = (lstrlenW(m_str) + 1) * sizeof(WCHAR);
        DWORD dwWritten;
        //BOMの書き込み - WindowsはIntel系でUTF-16 FF FE(リトルエンディアン)
        BYTE BOM[2] = {0xFF, 0xFE};
        if(WriteFile(hFile, BOM, 2, &dwWritten, NULL)) {
            WriteFile(hFile, m_str, dwTextLength, &dwWritten, NULL);
            bSuccess = TRUE;    //書込み成功
        }
        CloseHandle(hFile);            //ファイルのクローズ
    }
    return bSuccess;
}

//ファイルへの書き込み(規定値UTF-16 リトルエンディアン)
bool CSTRW::ToFile(WCHAR* FileName) {

    BOOL bSuccess = FALSE;
    HANDLE hFile;
    hFile = CreateFileW(FileName, GENERIC_WRITE, NULL, NULL,    //ファイルを書込みでオープン
                        CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if(hFile != INVALID_HANDLE_VALUE) {        //オープンできたか
        //バッファから文字列長取得し、NULL終端も加算
        DWORD dwTextLength = (lstrlenW(m_str) + 1) * sizeof(WCHAR);
        DWORD dwWritten;
        //BOMの書き込み - WindowsはIntel系でUTF-16 FF FE(リトルエンディアン)
        BYTE BOM[2] = {0xFF, 0xFE};
        if(WriteFile(hFile, BOM, 2, &dwWritten, NULL)) {
            WriteFile(hFile, m_str, dwTextLength, &dwWritten, NULL);
            bSuccess = TRUE;    //書込み成功
        }
        CloseHandle(hFile);            //ファイルのクローズ
    }
    return bSuccess;
}

//UTF-8ファイルへの書き込み(boolはBOM有無のフラグ)
bool CSTRW::ToUTF8File(WCHAR* FileName, bool bom = TRUE) {

    BOOL bSuccess = FALSE;
    HANDLE hFile;
    hFile = CreateFileW(FileName, GENERIC_WRITE, NULL, NULL,    //ファイルを書込みでオープン
                        CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if(hFile != INVALID_HANDLE_VALUE) {        //オープンできたか
        //WCHARをUTF8へ変換した際の文字列長(バイト)を取得
        DWORD dwBuffSize = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, m_str, -1, NULL, NULL, NULL, NULL);
        if(!dwBuffSize) {
            CloseHandle(hFile);                //ファイルのクローズ
            return FALSE;
        }
        //UTF-8文字列用メモリーの確保
        char* utf8 = new char[dwBuffSize];
        //WCHAR文字列をUTF8文字列へ変換
        WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, m_str, -1, utf8, dwBuffSize, NULL, NULL);
        //UTF-8文字列の書き込み
        DWORD dwWritten;
        if(bom) {    //BOM有り
            //BOMの書き込み - UTF-8 0xEF 0xBB 0xBF
            BYTE BOM[3] = {0xEF, 0xBB, 0xBF};
            if(WriteFile(hFile, BOM, 3, &dwWritten, NULL)) {
                WriteFile(hFile, utf8, dwBuffSize, &dwWritten, NULL);
                bSuccess = TRUE;    //書込み成功
            }
        }
        else {        //BOM無し
            if(WriteFile(hFile, utf8, dwBuffSize, &dwWritten, NULL))
                bSuccess = TRUE;    //書込み成功
        }
        delete [] utf8;                //UTF-8文字列メモリーの開放
        CloseHandle(hFile);            //ファイルのクローズ
    }
    return bSuccess;
}

//(解説:これはUTF-8専用の書き込み関数で、BOMの有無はフラグで指定します。WideCharToMultiByte関数も変換先をNULLにして「素振り」すると必要文字長を返してくれます。フラグでBOM有であれば先ずBOMを書き込み、BOM無しであればそのままデータを書き込みます。)


//m_strの単語を切り取り出す
bool CSTRW::Next(CSTRW& destination) {

    CSTRW str;
    delete [] str.m_str;
    str.m_str = new WCHAR[lstrlenW(m_str) + 1];
    WCHAR* ptr = m_str;
    WCHAR* des = str.m_str;
    bool Terminate = TRUE;
    //スペース文字はスキップする
    while(iswspace(*ptr) || *ptr == L' ' || *ptr == L',') {
            ptr++;                        //スキップスペース文字
    }
    //非スペース文字を調べる
    while(!iswspace(*ptr)) {            //スペース文字でない
        if(!*ptr) {                        //NULL終端ならFALSEを返す
            Terminate = FALSE;
            break;
        }
        else if(*ptr == L' ') {
            break;                        //全角" "なら終了
        }
        else if(*ptr == L',') {
            ptr++;                        //','をスキップして次の文字まで進む
            break;                        //空白文字扱い
        }
        else if(*ptr == L'"') {
            ptr++;                        //'"'をスキップして中身だけ取り出す
            while(*ptr != L'"') {        //'"'なら'"'まで進む
                if(!*ptr) {                //NULL終端ならFALSEを返す
                    Terminate = FALSE;
                    break;
                }
                *des = *ptr;
                ptr++;    des++;
            }
            ptr++;                        //ソースポインターは次の文字へ進める
            break;                        //ループを抜けてデスティネーションはNULLが入る
        }
        else if(*ptr == L'/' && *(ptr + 1) == L'*') {
            ptr++;    ptr++;                //"/*~*/"コメント
            while(!(*ptr == L'*' && *(ptr + 1) == L'/')) {
                if(!*ptr) {                //NULL終端ならFALSEを返す
                    Terminate = FALSE;
                    break;
                }
                ptr++;
            }
            ptr++;    ptr++;
            //再度スペース文字をスキップする
            while(iswspace(*ptr) || *ptr == L' ' || *ptr == L',') {
                if(!*ptr) {                //NULL終端ならFALSEを返す
                    Terminate = FALSE;
                    break;
                }
                ptr++;                //スキップスペース文字
            }
            continue;
        }
        else if(*ptr == L'/' && *(ptr + 1) == L'/') {
            while(*ptr != L'\n') {        //"//"コメント
                if(!*ptr) {                //NULL終端ならFALSEを返す
                    Terminate = FALSE;
                    break;
                }
                ptr++;
            }
            while(iswspace(*ptr) || *ptr == L' ' || *ptr == L',') {
                if(!*ptr) {                //NULL終端ならFALSEを返す
                    Terminate = FALSE;
                    break;
                }
                ptr++;                //スキップスペース文字
            }
            continue;
        }
        else {
            *des = *ptr;
        }
        ptr++;    des++;
    }
    *des = L'\0';

    destination = str;            //destinationに切り取った文字列を代入
    str = ptr;                    //strにm_strより進んだptr以降の文字列を代入
    delete [] m_str;
    m_str = new WCHAR[lstrlenW(str.m_str) + 1];
    lstrcpyW(m_str, str.m_str);    //m_strには切り取った後の文字列が入る
    if(*str.m_str || *destination.m_str)
        Terminate = TRUE;        //残がNULLだけでも切り取った文字列があれば続ける
    else
        Terminate = FALSE;        //NULLだけなら終わり
    return Terminate;            //成功・失敗フラグを返す
}

//この行末まで進む
bool CSTRW::GetLine(CSTRW& destination) {

    bool Terminate = TRUE;
    WCHAR* ptr = m_str;
    WCHAR* des = ptr;
    while(*des && *des != L'\n')
        des++;
    if(!*des) {
        destination = L"";
        Terminate = FALSE;    //文字列の終了サイン
    }
    else {
        *des = L'\0'; des++;                //NULL終端させ、一つ進める
        delete [] destination.m_str;
        destination.m_str = new WCHAR[des - ptr + 1];
        lstrcpyW(destination.m_str, ptr);
        destination.m_str[des - ptr - 1] = L'\n';    //NULLにした'\n'を付加
        destination.m_str[des - ptr] = L'\0';        //NULL終端
    }
    if(Terminate) {                    //まだ文字列があれば残りを入れる
        CSTRW str = des;
        delete [] m_str;
        m_str = new WCHAR[lstrlenW(str.m_str) + 1];
        lstrcpyW(m_str, str.m_str);    //m_strには切り取った後の文字列が入る
    }
    if(*destination.m_str)            //NULLに出会ったが、
        Terminate = TRUE;            //切り取った文字列があれば続ける
    else
        Terminate = FALSE;            //NULLだけなら終わり
    return Terminate;                //成功・失敗フラグを返す
}

//次の行頭まで進む
bool CSTRW::NextLine() {

    WCHAR* ptr = m_str;
    while(*ptr != L'\n') {        //改行コードまで進む
        if(!*ptr) {                //途中でNULL終端したならFALSEを返す
            return FALSE;
        }
        ptr++;
    }
    ptr++;
    CSTRW str = ptr;
    delete [] m_str;
    m_str = new WCHAR[lstrlenW(str.m_str) + 1];
    lstrcpyW(m_str, str.m_str);    //m_strには切り取った後の文字列が入る
    return TRUE;                //成功・失敗フラグを返す
}

//文字列の検索
WCHAR* CSTRW::Find(WCHAR* ToFind, WCHAR* sp = 0) {

    if(!sp)
        sp = m_str;
    return StrStrW(sp, ToFind);
}

#endif

 

こんな感じです。一応次のテストプログラムで正常に読み書き出来ました。

//Test for UTF-8
    CMNDLGW cmndlg;
    MessageBox(m_hWnd, "UTF-16ファイルを開いてください。", "FromFile", MB_OK | MB_ICONINFORMATION);
    WCHAR* fn = cmndlg.GetFileName(m_hWnd, L"テキストファイル(*.txt)\0*.txt\0\0", TRUE);
    CSTRW fl;
    fl.FromFile(fn);
    fl.Print();
    MessageBox(m_hWnd, "そのファイルをUTF-8で保存します。", "ToUTF8File", MB_OK | MB_ICONINFORMATION);
    fl.ToUTF8File(fn);
    MessageBox(m_hWnd, "再度そのUTF-8ファイルを開いてください。", "FromFile", MB_OK | MB_ICONINFORMATION);
    fl.FromFile(fn);
    fl.Print();
    MessageBox(m_hWnd, "そのファイルをもう一度BOM無しUTF-8で保存します。", "ToUTF8File", MB_OK | MB_ICONINFORMATION);
    fl.ToUTF8File(fn, FALSE);
    fl.ToUTF8File(L"BOM無しUTF8.txt", FALSE);
    MessageBox(m_hWnd, "再度そのBOM無しUTF-8ファイルを開きます。", "FromFile", MB_OK | MB_ICONINFORMATION);
    fl.FromFile(fn);
    fl.Print();
    MessageBox(m_hWnd, "そのファイルをまたUTF-16に戻します。", "ToFile", MB_OK | MB_ICONINFORMATION);
    fl = L"私の名前はBCCSkeltonです。My Name is BCCSkelton.\r\n私は20歳になります。I have become 20 years of age.\r\nブログを読んでくれてありがとう。Thank you for reading my weblog.";
    fl.ToFile(fn);

 

が、念のために(面倒くさいですけど)CSTRWを使って単純テキストエディターでも組んでみましょうか?