Unicode版のCSTRのUTF-16とUTF-8ファイルの読み書きについて、まだUTF-16はゴミ(どうも最後のCRLF-0D 00 0A 00が0D

00になったり、消えたりする)、UTF-8は謎の不具合(文字化けや文字列の短縮化が、同じ形式のファイルで起ったり、起らなかったりして完全な再現化ができていない)が続いており、ソースを見直してもおかしいところが無く、何故そうなるのか未だ分かりません。

ということで、CSTRの不具合はじっくりやるとして、BCCSkeltonWplus→ECCSkeltonへの変更部分の説明を先行させましょう。

 

【WCEditor.cpp】-ウィンドウクラス登録にコールバック関数アドレスが不要になりました

//////////////////////////////////////////
// WCEditor.cpp
//Copyright (c) 06/30/2022 by ECCSkelton
//////////////////////////////////////////
#include    "WCEditor.h"
#include    "WCEditorProc.h"

////////////////
// WinMain関数
////////////////
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPWSTR lpCmdLine, int nCmdShow) {    //WinMainがユニコードベース

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

    //ウィンドウ登録 - Init(ClassName, hInstance, (WndProcは不要,)"IDM_MAIN",
    //                        (以下省略可)"IDI_ICON"-これをLPWSTRにした, IDC_ARROW, Brush)
    WCEditor.Init(L"MainWnd", hInstance, L"IDM_MAIN", L"IDI_ICON");

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

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

 

【WCEditor.h】-一番変更点が多い

//////////////////////////////////////////
// WCEditor.h
// Copyright (c) 06/30/2022 by ECCSkelton
//////////////////////////////////////////
//ECCSkeltonのヘッダー-これに必要なヘッダーが入っている
#include    "..\ECCSkelton.h"    //今はこのパスに仮置き状態です
//リソースIDのヘッダー
#include    "ResWCEditor.h"

/////////////////////////////////////////////////////////////////////
//CMyWndクラスをCSDIクラスから派生させ、メッセージ用の関数を宣言する
/////////////////////////////////////////////////////////////////////
class CMyWnd : public CSDI
{
public:    //以下はコールバック関数マクロと関連している
    //2重起動防止用のMutex用ID名称
    CMyWnd(WCHAR* 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)
    //メニュー項目、ダイアログコントロール関連
    bool OnNew();
    bool OnOpen();
    bool OnSave();
    bool OnSaveas();
    bool OnExit();
    bool OnCut();
    bool OnCopy();
    bool OnPaste();
    bool OnUtf16();
    bool OnUtf8();
    bool OnUtf8n();
    bool OnVersion();
    //ウィンドウメッセージ関連
    CMDTABLE    //CMyWndクラスのOnCommand()関数宣言-これは追加です
    bool OnCreate(WPARAM, LPARAM);
    bool OnSize(WPARAM, LPARAM);
    bool OnClose(WPARAM, LPARAM);
    //ユーザー定義関数
    void ChangeCheck(int);
};

////////////////////////////////////////////////////////////////////////
//派生させたCMyWndクラスのインスタンスとOnCommand関数(マクロ)の作成
////////////////////////////////////////////////////////////////////////
CMyWnd WCEditor(L"WCEditor");    //ウィンドウクラスインスタンスの生成

//CMyWndクラスのOnCommand()関数定義-メッセージテーブル(BEGIN_SDIMSG)の代わりに入れます
BEGIN_CMDTABLE(CMyWnd)
    ON(IDM_NEW, OnNew())
    ON(IDM_OPEN, OnOpen())
    ON(IDM_SAVE, OnSave())
    ON(IDM_SAVEAS, OnSaveas())
    ON(IDM_EXIT, OnExit())
    ON(IDM_CUT, OnCut())
    ON(IDM_COPY, OnCopy())
    ON(IDM_PASTE, OnPaste())
    ON(IDM_UTF16, OnUtf16())
    ON(IDM_UTF8, OnUtf8())
    ON(IDM_UTF8N, OnUtf8n())
    ON(IDM_VERSION, OnVersion())
END_CMDTABLE


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

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

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

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

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

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

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

//VERSIONDLGクラスのOnCommand()関数定義-メッセージテーブルがコマンドテーブルになります
BEGIN_CMDTABLE(VERSIONDLG)
    ON(IDOK, OnIdok())
END_CMDTABLE

 

【WCEditorProc.h】-ここは更に少ないので、変更部分のみ

bool CMyWnd::OnVersion() {

    //IDD_VERSIONダイアログを呼び出す-コールバック関数アドレスが不要になりました
    versiondlg.DoModal(m_hWnd, L"IDD_VERSION");
    return TRUE;

}

 

WCEditorでこの程度の変更でBCCSkeltonベースのプログラムが (完全ワイド文字対応のBCC102専用)ECCSkeltonベースに変更ができます。

本日BatchGoodのターゲットオプションを↓の様に変更してアップしましたので、追っ付けDLできるようになります。内容はECCSkeltonを使ったユニコードプログラムをコンパイルできるように"-tC"、"-tW"、"-tD"オプションに"U"を付けた("-tCU"、"-tWU"、"-tDU")だけです。(笑)

 

 

ps. なお、COMBOBOXのドロップダウンリストの幅を変えました。これはコンボボックスへ(最小許容幅をwParamに入れて)CB_SETDROPPEDWIDTHメッセージを送って設定します。

早速ECCSkeltonでコンパイルしたWCEditorの解説に行きたかったのですが、UTF-16テキストの読み込みでゴミ(末尾の■)、UTF-8テキストの書き込みで文字列が短縮化されるようなので、それをフィクスしようと思います。

 

先ずは現在のWindowsのベースとなるUTF-16対策ですね。

ECCSkelton用のCSTRクラスをリビューしましたが、おかしなところはありません。しかし、↓のサンプルテキストファイルを読み込むとEDITコントロールの末尾に「■」が現れます。

このテキストファイルの中身がどうなっているのか、更にダンプを取って調べます。

 

1.UTF-16のテキスト

BCCForm and BCCSkeltonブログを読んでいただいてありがとうございます。
これからもよろしくお願いいたします。
改行)」
 

2.ダンプリスト

0000 FF FE 42 00 43 00 43 00 :46 00 6F 00 72 00 6D 00 :..B.C.C.F.o.r.m.
0010 20 00 61 00 6E 00 64 00 :20 00 42 00 43 00 43 00 :..a.n.d...B.C.C.
0020 53 00 6B 00 65 00 6C 00 :74 00 6F 00 6E 00 D6 30 :S.k.e.l.t.o.n..0
0030 ED 30 B0 30 92 30 AD 8A :93 30 67 30 44 30 5F 30 :.0.0.0...0g0D0.0
0040 60 30 44 30 66 30 42 30 :8A 30 4C 30 68 30 46 30 :.0D0f0B0.0L0h0F0
0050 54 30 56 30 44 30 7E 30 :59 30 02 30 0D 00 0A 00 :T0V0D0.0Y0.0....
0060 53 30 8C 30 4B 30 89 30 :82 30 88 30 8D 30 57 30 :S0.0K0.0.0.0.0W0
0070 4F 30 4A 30 58 98 44 30 :44 30 5F 30 57 30 7E 30 :O0J0X.D0D0.0W0.0
0080 59 30 02 30 0D 00 0A 00 :00 00 00 00 00 00 00 00 :Y0.0............
<説明>

ダンプの8バイトコードはIntelチップがリトルエンディアンなのでひっくり返って格納されます。(例:FEFF→FF FE)

FEFF → UTF-16 BOM

U+3059 → 「す」

U+3002 → 「。」

U+000D → CR(注)

U+000A → LF(注)

注:改行コードはOSによって異なります。WindowsはCR+LF、MacはCRのみ、UnixはLFのみです。

 

このようになーんも問題のないデータですが、文字化け■が出ますね。なんででしょう?まぁ、ゆっくりやりますか。

 

 

前回は非常に短くシンプルな「雪原のウサギ」プログラムを載せましたが、あれは「ウィンドウプログラムがこーんなに短かいんだー!」と思っていただくための(あまり意味のない)プログラムでした。実際のプログラムにはアクセラレーター、メニュー、コントロールが付けられますが、それらはIDを使って親ウィンドウに(WM_なんちゃらの)通知メッセージで割り込みをかけますので、コールバック関数に「メッセージテーブル」を持つ必要があります。(注)

注:BCCSkeltonではウィンドウやダイアログの基底クラスから派生させたCMyWndクラス等に割り込み処理関数をメンバーとして持たせ、コールバック関数を外部関数にしてメッセージテーブルをマクロでつくり、次のようにウィンドウやダイアログで別々のマクロテーブルを持っていました。。

(1)ウィンドウSDI

BEGIN_SDIMSG(インスタンス名)    //ダイアログと違い、コールバック関数名を特定しない
    //メニュー項目、ダイアログコントロール関連

   .

   .

   .

    ON_COMMAND(インスタンス名, ID名, On関数名())
    //ウィンドウメッセージ関連
    ON_CREATE(インスタンス名)

   .

   .

   .

END_WNDMSG

(2)MDIウィンドウ

BEGIN_MDIMSG(インスタンス名)    //ダイアログと違い、コールバック関数名を特定しない
    //メニュー項目、ダイアログコントロール関連
 (略)

    //ウィンドウメッセージ関連
    //MDIFrame親ウィンドウは自動的にOnMDIFrameCreate()を呼ぶ

 (略)

END_MDIMSG(インスタンス名)
//ウィンドウメッセージは全てフレームウィンドウにあるので、
//必要なメッセージ関数を子供ウィンドウへ移すことが必要
BEGIN_MDICHILDMSG(インスタンス名)    //ダイアログと違い、コールバック関数名を特定しない

 (略)

END_MDICHILDMSG

(3)ダイアログ

BEGIN_MODELESSDLGMSG(コールバック関数アドレス, インスタンス名) 
または

BEGIN_MODALDLGMSG(コールバック関数アドレス, インスタンス名) 
    //メニュー項目、ダイアログコントロール関連

 (略)

    //ウィンドウメッセージ関連
    //自動的にダイアログ作成時にOnInit()、終了時にOnClose()を呼びます

 (略)

END_DLGMSG
 

ECCSkeltonでは、(合図が大きくなり、余計な処理で速度的にはやや不利ですが)ベースのコールバック関数をウィンドウクラスに共通の静的関数(注)とし、(それぞれのウィンドウを持つ)そのクラスのインスタンスに固有のコールバック関数をクラスのメンバー関数にしてベースのコールバック関数からウィンドウ毎に呼び出す形をとっています。これにより、サイズ、スピードと引き換えにメッセージテーブルを内部に持つことができます。

一方、メニューやコントロールは(従ってそれらのIDも)プログラム毎に固有のものであり、予め定義しておくことはできません。これらはWM_COMMANDメッセージの際に呼び出される"OnCommand(WPARAM, LPARAM)"関数の中にプログラム毎に定義する必要があります。具体的には、

(1)ウィンドウまたはダイアログのクラスの中のメンバー関数に、

   bool OnCommand(WPARAM, LPARAM);

   と種々のコントロールに関わる呼び出し関数

   例:bool OnCreate(WPARAM, LPARAM);

   を宣言し、

(2)その関数の定義の中で、種々のコントロールに関わる呼び出し関数を呼びます。

   (例)

   bool (クラス名)::OnCommand(WPARAM wParam, LPARAM lParam) {

      bool Done = FALSE;

      switch(LOWORD(wParam)) {      //メニューやコントロールIDの値

      case IDM_(メニューの場合):

         Done = On〇〇〇();

         break;

      case IDC_(コントロールの場合):

         Done = On〇〇〇();

         break;

      }

      return Done;

   }

という処理をプログラム毎に書く必要があります。

ECCSkeltonでは、コマンド関数を含むウィンドウメッセージのテーブルとメッセージ毎の呼び出し関数はクラスの中に予め宣言され、仮想関数で定義されていますので、派生クラスのなかでコマンド関数と定義が必要なメッセージ毎の呼び出し関数をオーバーライド関数で定義しますが、これらのタイプ量を減らす意味でこれもマクロ化(注)しました。

注:CMDTABLE → クラス宣言のなかのOnCommand(WPARAM, LPARAM);の代替マクロ

  BIGIN_CMDTABLE(クラス名) →OnCommand関数の定義開始部分

  END_CMDTABLE → OnCommand関数の定義終了部分

説明するとなんだかわかりませんので、以下にIDM_MENUというメニューとIDI_ICONというアイコンのリソースを持つウィンドウプログラムをサンプルとして載せます。

 

【CSDITest.cpp】

/////////////////////////
// インクルードファイル
/////////////////////////

#include    "..\ECCSkelton.h"
#include    "ResCSDITest.h"

/////////////////////////
// 旧BCCSkeltonにおける
// ".h"ファイル部分
/////////////////////////
//CMyWndクラスをCSDIから派生させ、OnCommandを定義する

class CMyWnd : public CSDI
{
public:
    CMyWnd(WCHAR* UName) : CSDI(UName) {};
    CMDTABLE    //CMyWndクラスのOnCommand()関数宣言
    bool OnCreate(WPARAM, LPARAM);
    bool OnClose(WPARAM, LPARAM);
    bool OnExit();
    bool OnVersion();
};

//CMyWndインスタンスをメインウィンドウとして作成する
CMyWnd MainWnd(L"ECCSkeltonTest");

//CMyWndクラスのOnCommand()関数定義
BEGIN_CMDTABLE(CMyWnd)
    ON(IDM_EXIT, OnExit())
    ON(IDM_VERSION, OnVersion())
END_CMDTABLE


//VERSIONDLGクラスをCDLGから派生させ、OnCommandを定義する
class VERSIONDLG : public CDLG
{
public:
    CMDTABLE    //VERSIONDLGのOnCommand()関数宣言
    bool OnIdok();
};

//VERSIONDLGインスタンスをversiondlgダイアログとして作成する
VERSIONDLG versiondlg;

//VERSIONDLGクラスのOnCommand()関数定義
BEGIN_CMDTABLE(VERSIONDLG)
    ON(IDOK, OnIdok())
END_CMDTABLE


///////////////
//外部変数宣言
///////////////
//CMNDLGクラスインスタンス

CMNDLG g_CmnDlg;

//CSBARクラスインスタンス
CSBAR g_SBar;

//EDITコントロール用CCTRLクラスインスタンス
CCTRL g_Edit;

////////////////////////
// 旧BCCSkeltonにおける
// "Proc.h"ファイル部分
////////////////////////
//CMyWndのOnCreate関数を定義する

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

    MessageBoxW(m_hWnd, L"OnCreate関数にいます", L"WM_CREATE", MB_YESNO | MB_ICONINFORMATION);
    return TRUE;
}

//CMyWndのOnClose関数を定義する
bool CMyWnd::OnClose(WPARAM wParam, LPARAM lParam) {

    if(MessageBoxW(m_hWnd, L"終了してもよいですか", L"終了確認",
        MB_YESNO | MB_ICONQUESTION) == IDYES)
        return TRUE;
    else
        return FALSE;
}

//CMyWndのOnExit関数を定義する
bool CMyWnd::OnExit() {

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

//CMyWndのOnVersion関数を定義する
bool CMyWnd::OnVersion() {

    versiondlg.DoModal(m_hWnd, L"IDD_VERSION");
    return TRUE;
}

//VERSIONDLGのOnIdok関数を定義する
bool VERSIONDLG::OnIdok() {

    EndModal(TRUE);
    return TRUE;
}

////////////////
// WinMain関数
////////////////

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPWSTR lpCmdLine, int nCmdShow) {

    //CSDIインスタンスの初期化
    if(!MainWnd.Init(L"ECCSkeltonTest", hInstance, L"IDM_MENU", L"IDI_ICON"))
        return 1;
    //SDIウィンドウの作成
    if(!MainWnd.Create(L"CSDITest"))
        return 1;
    //メッセージループに入る
    return MainWnd.Loop();
}


 

如何でしょうか?(頑張ってサクラエディタ―の通りに色分けしてみました。また、wWinMainも強調しています。)

コメントで書いている通り、BCCSkeltonの構造に合わせてECCSkeltonのプログラムが書けることがお分かりだと思います。これにより、BCCFormとSkeltonWizardがECCSkeltonでも最小の修正で依然活用できることになります。

 

「最小限の修正」とは以下の内容です。

(1)BCCSkelton.h → ECCSkelton.h

(2)char → WCHAR

(3)"(文字列)" → L"(文字列)"

(4)マクロ部分

   CMDTABLE → クラス宣言のメンバー関数宣言部に新設

   BIGIN_CMDTABLE(CMDTABLEクラス名) → 旧マクロBIGIN_〇〇の入れ替え

   旧ON_CREATE等のウィンドウメッセージ関連マクロは廃止

   旧ON_COMMAND(インスタンス名, ID, On関数名(<引数オプション>))マクロ → ON(ID, On関数名(<引数オプション>))マクロへ移行

   END_CMDTABLE → 旧マクロEND_〇〇の入れ替え

(5)外部コールバック関数アドレスの引数

   メンバー関数化に伴い、ECCSkeltonから引数を廃止します。

 

次回に前にBCCSkeltonWplusでやったWCEditorをECCSkeltonに移植します。(注)

注:既に移植は完了し、正常に動作していますが、テスト中に前に気が付かなかった不具合が分かりましたので、それを修正してから紹介します。

 

今回のテーマはBCC102(Embarcadero C++ Compiler)とECCSkeltonのダブルヘッダーです。

 

しこしことUTF-16ベースのECCSkeltonプロジェクトを進めていますが、基本の".cpp"ファイルの記述をBCCSkeltonのLPSTR(char*のことです)

 

【BCCSkeltonで使っているnon-Unicordのchar*ベースのエントリーポイント】
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPSTR lpCmdLine, int nCmdShow) {
 

のままにするわけにはいかず、Microsoft Docで調べたら、ユニコード用のエントリーポイントはwWinMainというらしいのでそのように書き換えました。

 

【ユニコード用のウィンドウズプログラムのエントリーポイント】

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPWSTR lpCmdLine, int nCmdShow) {
 

所が(どっこい)、VC++で通る筈のこれが、BCC102ではリンク時に"WinMainが見当たらない"エラーになります。

<エラーメッセージ>

----------------------------------
 BatchGood - Batch File Generator
 for Embarcadero "bcc32c.exe"
 Copyright (c) 2021 by Ysama
----------------------------------
Embarcadero C++ 7.30 for Win32 Copyright (c) 2012-2017 Embarcadero Technologies, Inc.
C:\Users\ysama\Programing\Windows Program\ECCSkelton\PlainWindow\PlainWindow.cpp:
Turbo Incremental Link 6.90 Copyright (c) 1997-2017 Embarcadero Technologies, Inc.

Error: Unresolved external 'WinMain' referenced from C:\BORLAND\BCC102\LIB\WIN32C\DEBUG\C0W32.OBJ
Error: Unable to perform link

bcc32c.exe: error: linker command failed with exit code 2 (use -Xdriver -v to see invocation)


では(「二股対応」は余り好きではないし、UTF-16一本やりのECCSkeltonの方針にも反しますが)TCHAR環境ではこのような二刀流が主流であり、これは(ユニコードでも、そうでなくとも)通ります。(当たり前か。)

【TCHAR環境でのウィンドウズプログラムのエントリーポイント】

#define        UNICODE        //ユニコードにするにはこれが必要です
int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPTSTR lpCmdLine, int nCmdShow) {

 

矢張り、(妥協を嫌う、頑固な)性格的にこれは受け付けがたいために、ひつこくDOSコンソールでilink32とbcc32cの英文ヘルプを探しますが、"Unicode"やら"wWinMain"やらの記述は全くなし。ウェブでも照会サイトやEmbarcaderoやMicrosoftDoc等を見てもヒントになりそうなものは何もなし。

 

流石に諦めかけてきたところで、bcc32cの英文ヘルプの最後の”Use '-h <OPT>' for help on a specific option”に一文で目が留まります。で、恐る恐る"bcc32c -h -t"と打ち込むと、

 

でたーっ!隠れヘルプがあんじゃないっ!!!

 

C:\Borland\BCC102\bin>bcc32c -h -t
Embarcadero C++ 7.30 for Win32 Copyright (c) 2012-2017 Embarcadero Technologies, Inc.
Available options (* = default setting, xxx = has sub-options: use -h -X):
(Note: -X- or -w-XXX will usually undo whatever was set or unset by -X or -wXXX.
 If two options conflict, the last one specified will be used.)
  -t      Specify target executable
  -tC     Target is a console application
  -tW     Target is a Windows application
  -tV     Target uses the VCL GUI framework
  -tR     Target uses the dynamic RTL
  -tD     Generate a shared library
  -tJ     Target uses Delphi
  -tP     Generate a package
  -tM     Target is multi-threaded
 
-tU     Generate a Unicode application
Note: sub-options for '-t' can be combined into a single option (-XA -XB => -XAB).

 

ということで、bcc32c命令の"-tW"オプションに"-tU"を付けて("Note"の通り、表記は”-tWU”になります)みたら、↓の最小限のウィンドウスケルトンプログラムがきれいにコンパイルされましたー!!!

 

/////////////////////////
// インクルードファイル
/////////////////////////
#include    "..\ECCSkelton.h"

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

 

    //CSDIインスタンスの宣言
    CSDI MainWnd(L"ECCSkelton_Test");
    //CSDIインスタンスの初期化
    if(!MainWnd.Init(L"PlainWindow", hInstance))
        return 1;
    //SDIウィンドウの作成
    if(!MainWnd.Create(L"PlainWindow"))
        return 1;
    //メッセージループに入る
    return MainWnd.Loop();
}

(なーんもないウィンドウなので雪原のウサギ状態です。)

(BCCSkeltonと似たか寄ったかで)「めんどーくせーなー」、と始めたECCSkeltonですが、色々とトラブルがあり、トラブルシューティングに嵌って結構時間つぶしができています。

 

<トラブルその1-純粋仮想関数>

C++でクラスを作る際に、"virtual"を付けたメンバー関数の宣言だけして"= 0;"を付けると純粋仮想関数となり、派生クラスで定義をすればよいのはご高尚の通りです。20年離れていたので、純粋仮想関数は放っておけば眠ったままか、と思ったのですが、派生クラスで定義しないと「〇〇は抽象基底クラスで、関数の定義がされていません。」というお叱りを受けます。まぁ、これも派生クラスで純粋仮想関数を維持すればよいのでしょうが、BCCSkeltonでカバーしている主要WM_メッセージだけでもいっぱいあるので毎回ズラズラ書くのもしんどく、純粋仮想関数は断念して単純にオーバーライドが効くデフォールトの仮想関数にしてズラッと並べてみました。(注)

注:c++11から、オーバーライドする派生クラスの関数には明示的に"override"キーワードを付けられるようです。

  例:(基底クラス)virtual bool foo() {such and such};

    (派生クラス)bool foo() {so and so} override;

  みたいな感じです。また、派生先でオーバーライドを禁止する"final"キーワードも付けられるようです。更に

  「以前は仮想関数であることを示すために派生クラスでも virtual を書く場合がありましたが、

   現在では override または final を使い、virtual は使いません。」

  だそうです。フ~ン、色々と変わってきているんですね。(因みに現在の段階ではまだ"override"を使っていません。)

 

<トラブルその2-CDLGクラスインスタンスの"this"の引き渡し>

BCCSkeltonではCWNDやCDLGや派生クラスのコールバック関数を外部関数にしていました。(速いし、コンパクトだから。)

ECCSkeltonではクラスの静的関数からインスタンス(ウィンドウ)別のメンバーコールバック関数を呼ぶ形にしてみたのですが、モーダルダイアログのDialogBoxなどではウィンドウハンドルが戻り値で帰ってこないのでウィンドウのプロパティにクラスインスタンスの"this"を書き込めません。

これは悩んだのですが、DialogBoxParamという良い関数があり、WM_INITDIALOGのlParamに"this"を与えることで解決しました。これはモードレスダイアログでもCreateDialogParamで同様に採用しました。(注)

注:    if(Message == WM_INITDIALOG) {
            //ウィンドウハンドルを取得し、
            ((CDLG*)lParam)->m_hWnd = hWnd;
            //ウィンドウプロパティにCDLGクラスインスタンスへのポインターを書込む(重要)
            SetPropW(hWnd, L"CPP_CLASS", (HANDLE)lParam);
        }

 

<トラブルその3-WM_CREATEでステータスバーやEDITコントロールが作れない?>

CSDIのテストプログラムで、WM_CREATEに対応するOnCreate(WPARAM, LPARAM)関数でステータスバーやEDITコントロールを作ろうとしたのですが、エラーになります。原因が分からず、

「なんでエラーになるんじゃいっ!!!」

と切れましたが、今朝早朝「あっ、WM_CREATEが終わるまでCreateWindowExW関数の戻り値が返されないなら、OnCreate関数の中ではm_hWndは'0(ゼロ)'のままかも?」というひらめきが降りてきて、↑のトラブルその2の応用で「CreateWindowExW関数の"PVOID  pParam"引数に"this"を与えればいいじゃん!」ということでやってみましたがうまく動きません。「ん?」ということで、与える時の"this"の値と、WM_CREATEのlParamの値を比べると全く別物!

という訳で後からMSN Docを読む(笑・泣;)と「WM_CREATEの際のlParamはCREATESTRUCTW構造体へのポインタ」であり、pParamの値はこの構造体のLPVOID lpCreateParamsメンバーに入っているそーな。という訳でCDLGの時の作法をそのまま移植して、

    if(Message == WM_CREATE) {    //最初のWM_CREATEで、
        //m_hWndを取得し(これをしないとOnCreateで失敗する)、
        ((CSDI*)((CREATESTRUCT*)lParam)->lpCreateParams)->m_hWnd = hWnd;
        //クラスインスタンスポインターを受け取りウィンドウプロパティに書込む(重要)
        SetPropW(hWnd, L"CPP_CLASS", (HANDLE)((CREATESTRUCT*)lParam)->lpCreateParams);
    }

というキャスト、キャストの「寿限無、寿限無」的記法で解決し、きちんとステータスバー、EDITコントロールが表示できました。

 

<今後どうする?>

何か、ECCSkeltonのテスト用に書いてきたCSDITest.cppというプログラムがBCCSkeltonWplusのWCEditorに酷似してきたので、ECCSkeltonのコマンドマクロ(注)を書きながら、テストプログラムとしてWCEditorのECCSkelton版にしてみようかな、と考えています。BCCSkeltonとの互換性もあるし、BCCSkeltonWplusで書いたクラスヘッダーもし利用できそうです。

注:BCCSkeltonではコールバック関数を外部関数として、それをマクロテーブルにしましたが、ECCSkeltonではメンバー仮想関数のOnCommand(WPARAM, LPARAM)関数の中でコントロールIDに応じた"Onなんちゃら()"関数を呼ぶので、そこの部分はマクロ化しようかなと考えています。

 

 

ECCSkeltonなどと、ほざいくまでは良かったが、試しにCWNDとCSDIまで作って、BCCSkeltonでは外部関数をマクロテーブルにしたコールバック関数もクラス内に仮想関数として定義し、メニュー、アクセラレーターやコントロールだけは派生させるクラスでOnCommand(WPARAM, LPARAM)関数を再定義させて、そこだけはマクロ化しようかな、とプロトタイプを作ったまでは良かったのですが...

 

前回画像を載せた「純粋ウィンドウスケルトン」(CSDITest.exe)が

 

 

なーんと100KB迄成長してしまった!

これじゃ、使い物にならないかな?と投げ出しかけたのですが、今朝CSDIでスケルトンだけをBCCSkeltonで作ってみたらやはり83KBにはなるので、申すこし遊んでみようかな?と思い直しました。(とはいっても、最終成果物は使い勝手の悪いBCCSkeltonの同等物なのでやはり萎えますよね。何か良いネタはないものかしら?)

 

Diceを作ってから、なんか「セミ(ミーン、ミーンじゃない!)燃え尽き症候群」となり、ボーっと生きてました。

 

しかし、本ブログの最終目的は「ボケない為の脳トレ」なので、何かキーをたたく気力、体力、知力を注ぐことのできるネタを考えていて、20年前のBCCSkeltonから脱却して「Embarcadero C++ Compiler用のスケルトン、ってーどーよ?」と思い至りました。

といっても、BCCSkeltonの環境(ソースを事故で失っているBCCFormやSkeltonWizard)のようなものはToomuchなので、「単なるエディターでもシコシコッと書ける手書き簡単クラスライブラリー」程度ならやってもいいかな、と感じてきました。

 

しかし、過去遺産(特にSkeltonWizardの排出する自動コード)はもったいないので可能な限り使えるようにして、一方現在見かけなくなったMDI等はもう廃止し、Windows用なので完全にUTF-16対応とする(多くのBCCSkeltonWplusのコードが再利用できる)ことにしてはどうかな、と考えました。

 

まぁ、一挙にBCCSkelton並みにライブラリーが充実することはないとしても、互換性があり、手軽な内容なら面白いかもしれませんね。

 

(画像はECCSkeltonのCSDIクラスのインスタンス例)

 

 ... Well, I don't know. (稲妻を打ち負かせるのかい?...さてね。)

 

前回の

【またまた、謎】Out of the blue ~ 青天の霹靂

をご覧ください。(ある日突然Albumが落ちる、という話です。)

 

BCC102にはデバッグ環境が無いので、トラップ型のデバッグツール(CDEBUGクラス-末尾参照)を使って何が起こっているのかを確認しました。

 

1.最初は「ファイルを開く」の辺りから始めて、ユーザー定義のReadAlbum関数の*.albファイルを読むところを調べました。(一部読めないデータがあり、FileListが落ちることもあるので、これも更によく調べてみないとならないのですが)とりあえずこれではないようです。

2.次にユーザー定義のShowAlbum関数をチェック、個々ではないのでCALBUMクラスのメンバー関数ShowImg(int x, int y, int w, int h)にたどり着きます。

3.前回問題になったchar*やWCHAR変換等は無事にパス。次にImage->DrawImageの描画が問題か、と思いましたが、それ以前で落ちています。

4.最後に分かったことは、「縦横比をdoubleで求めているのですが、それを出すためにImageクラスのメソッドであるImage->GetWidth()Image->GetHeight()のところで(今まではきちんとイメージの幅と高さをUINTで返していたのですが)'0'が返ってきており、この結果"Division by zero"エラーが生じ(従ってプログラムが落ち)ていることが分かりました。

 

しっかし、(コンパイルエラーも無く)何故ある日突然GetWidth()、GetHeight()メソッド(メンバー関数)が値を返さなくなったのかは依然不明で解決にはなりません。GDI+サイドの話なので時間がかかりそうです。(従ってAlbumプログラムは変更しません。)

 

追記:気になったので、C:\Windows\System32に入っているGdiPlus.dllを調べたら、矢張り「ある日突然」の感じに近い「2022年5月13日の午前8:17に更新」されていますね。"Windows Updateかな?"という勘は結構当りだったのかも。

追々記:更にWindows Update情報を確認すると、

(1)現在のGdiPlus.dllは2022年5月13日のKB5013542で交換されたファイルバージョン10.0.19041.1706です。

(2)正常に動いていたと考えられる時のGdiPlus.dllは2022年4月14日のKB5012599で交換されたファイルバージョン10.0.19041.1645です。

(3)それ以前にも2/11、2/18、3/9、3/10(x86ベースのすべてのファイル-)、4/5(x86ベースのすべてのファイル)とWin10のアップデートが行われているのでGdiPlus.dllも変更されている可能性が高いです。

注:この段階で次のような告知が行われていたのですね。知りませんでした。

3/15/22
重要なWindows 10、バージョン 20H2 は、ワークステーションエディションのホーム、Pro、Pro Education、およびProを実行するデバイスのサービスが 2022 年 5 月 10 日に終了します。 2022 年 5 月 10 日以降、これらのデバイスは、最新のセキュリティ脅威からの保護を含む毎月のセキュリティと品質更新プログラムを受け取らなくなります。 セキュリティと品質の更新プログラムを引き続き受け取るには、最新バージョンのWindows 10またはWindows 11に更新することをお勧めします。」

となると、もう20H2のWin10は現在のままで(今回のGetWidth()やGetHeight()の問題を含む)今後のバグフィクスもない、ということなのでしょうか?でしょうね。やり切れんなぁ。

 

【ご参考-CDEBUGクラス】

BCCForm and BCCSkeltonプロジェクトの<プロジェクト>.hファイルのインクルードファイル("BCCSkelton.h"とリソースIDのヘッダー:<Resプロジェクト>.h)の後の3番目に

#include "CDEBUG.h"

として以下のファイルを入れ、インスタンスを生成しておきます。

CDEBUG g_Debug;        //外部変数として定義
そしてOnInit()やOnCreate()で、初期化します。

g_Debug.Init(m_hWnd);

エラー原因がありそうなところに"g_Debug.Print(<引数>);"を置いて、モニターしたい様々な変数を引数に置いて調べます。(凝った使い方ではダイアログのタイトルや表示文字もprintfと同じ文字列で指定できます。)

エラー原因ヶ所を突き止めたならば、最後にエラーメッセージをErrorMsg()で確認します。

 

////////////////////////////////////////////////////
//CDEBUGクラス
//BCCSkeltonプログラミング用デバッグツール集
//注意:最初にウィンドウハンドルで初期化する
//        例-debug.Init(m_hWnd);
//Print関数   -プログラム中宣言して確認したい変数を
//                引数に渡し、"printf"と同じ表示フォー
//                マットで表示する
//                対象変数はchar*、WCHAR、整数、長整数
//ErrorMsg関数-GetLastErrorを取得し、FormatMessage
//                の文字列を表示
////////////////////////////////////////////////////
#include    <stdio.h>        //floatとdouble用

class CDEBUG
{
private:
    HWND m_hWnd = NULL;                            //対象ウィンドウハンドル
    char m_Buffer[MAX_PATH * 4];                //表示文字列用バッファー(1,040バイト)
public:
    CDEBUG();                                    //引数無しコンストラクター
    CDEBUG(HWND);                                //引数付きコンストラクター
    void Init(HWND hWnd) {m_hWnd = hWnd;};        //対象ウィンドウハンドルで初期化する
    void Print(char*, char*, char*);            //文字列表示用
    void Print(WCHAR*, WCHAR*, WCHAR*);            //Unicode文字列表示用
    void Print(int, char*, char*);                //整数表示用
    void Print(long int, char*, char*);            //長整数表示用
    void Print(WORD, char*, char*);                //符号なし整数表示用
    void Print(DWORD, char*, char*);            //符号なし長整数表示用
    void Print(float, char*, char*);            //単精度浮動小数点表示用
    void Print(double, char*, char*);            //倍精度浮動小数点表示用
    void ErrorMsg();                            // ErrorMsg (Error Message) 関数
};

//引数付きコンストラクター
CDEBUG::CDEBUG() {
}

//引数付きコンストラクター
CDEBUG::CDEBUG(HWND hWnd) {
    Init(hWnd);
}

//文字列表示用
void CDEBUG::Print(char* Arg, char* Title = "Debug char*", char* Format = "%s") {

    wsprintf(m_Buffer, Format, Arg);
    MessageBox(m_hWnd, m_Buffer, Title, MB_OK | MB_ICONINFORMATION);
}

//Unicode文字列表示用
void CDEBUG::Print(WCHAR* Arg, WCHAR* Title = L"Debug WCHAR", WCHAR* Format = L"%s") {

    wsprintfW((WCHAR*)m_Buffer, Format, Arg);
    MessageBoxW(m_hWnd, (WCHAR*)m_Buffer, Title, MB_OK | MB_ICONINFORMATION);
}

//整数表示用
void CDEBUG::Print(int Arg, char* Title = "Debug int", char* Format = "%d") {
    wsprintf(m_Buffer, Format, Arg);
    MessageBox(m_hWnd, m_Buffer, Title, MB_OK | MB_ICONINFORMATION);
}

//長整数表示用
void CDEBUG::Print(long int Arg, char* Title = "Debug long int", char* Format = "%ld") {
    wsprintf(m_Buffer, Format, Arg);
    MessageBox(m_hWnd, m_Buffer, Title, MB_OK | MB_ICONINFORMATION);
}

//符号なし整数表示用
void CDEBUG::Print(WORD Arg, char* Title = "Debug WORD", char* Format = "%d") {
    wsprintf(m_Buffer, Format, Arg);
    MessageBox(m_hWnd, m_Buffer, Title, MB_OK | MB_ICONINFORMATION);
}

//符号なし長整数表示用
void CDEBUG::Print(DWORD Arg, char* Title = "Debug DWORD", char* Format = "%ld") {
    wsprintf(m_Buffer, Format, Arg);
    MessageBox(m_hWnd, m_Buffer, Title, MB_OK | MB_ICONINFORMATION);
}

//単精度浮動小数表示用
void CDEBUG::Print(float Arg, char* Title = "Debug float", char* Format = "%f") {
    sprintf(m_Buffer, Format, Arg);
    MessageBox(m_hWnd, m_Buffer, Title, MB_OK | MB_ICONINFORMATION);
}

//倍精度浮動小数表示用
void CDEBUG::Print(double Arg, char* Title = "Debug double", char* Format = "%lf") {
    sprintf(m_Buffer, Format, Arg);
    MessageBox(m_hWnd, m_Buffer, Title, MB_OK | MB_ICONINFORMATION);
}

// ErrorMsg (Error Message) 関数
void CDEBUG::ErrorMsg() {
    
    DWORD err_code = GetLastError();
    LPVOID lpMsg;                                    //メッセージ用テキストバッファポインタ
    FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER    //メッセージ用のメモリ要求
        | FORMAT_MESSAGE_FROM_SYSTEM                //Windowsのエラーメッセージを検索
        | FORMAT_MESSAGE_IGNORE_INSERTS,            //メッセージの挿入、最後引数を無視
        NULL, err_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),    //無視、エラーコード、言語指定
        (LPTSTR)&lpMsg, 0, NULL);                    //バッファへポインタ、不要、無視(挿入数値)        
    MessageBox(m_hWnd, (LPTSTR)lpMsg, "エラー", MB_OK | MB_ICONEXCLAMATION);
    LocalFree(lpMsg);                                //メッセージテキスト用メモリーの解放
}

 

今回は最後のDiceProc.hの解説です。

 

//////////////////////////////////////////
// DiceProc.h
// Copyright (c) 05/21/2022 by BCCSkelton
//////////////////////////////////////////

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

    //コモンコントロールの初期化
    InitCommonControls();
    //使用言語の設定
    if(MessageBox(m_hWnd, "\"Yes\" selects Japanese, while \"No\" does English.\r\n「はい」は日本語、"\
                    "「いいえ」は英語を選択します。", "Choise of Language - 言語の選択",
                    MB_YESNO | MB_ICONQUESTION) == IDYES)
        g_JAPANESE = TRUE;

//(解説:ダイアログを作る際にデフォルト英語を日本語にするか否か確認します。日本語の場合は日本語フラグを立てます。)

    //メインダイアログやコントロールのフォントを日本語へ変更する
    if(g_JAPANESE) {
        g_Font.SetFont(m_hWnd, 8, "MS 明朝");
        SendItemMsg(IDC_EDIT, WM_SETFONT, (WPARAM)g_Font.GetFont(), (LPARAM)TRUE);
        SendItemMsg(IDC_STATUSBAR, WM_SETFONT, (WPARAM)g_Font.GetFont(), (LPARAM)TRUE);
        SendMsg(WM_SETTEXT, 0, (LPARAM)Str_10);
        SendItemMsg(IDC_HOWTOUSE, WM_SETTEXT, 0, (LPARAM)Str_11);
        SendItemMsg(IDC_ROLL, WM_SETTEXT, 0, (LPARAM)Str_12);
        SendItemMsg(IDOK, WM_SETTEXT, 0, (LPARAM)Str_13);
    }
/* ダイアログのデフォルトが無ければこれが必要
    else {
        g_Font.SetFont(m_hWnd, 8, "Times New Roman");
        SendItemMsg(IDC_EDIT, WM_SETFONT, (WPARAM)g_Font.GetFont(), (LPARAM)TRUE);
        SendItemMsg(IDC_STATUSBAR, WM_SETFONT, (WPARAM)g_Font.GetFont(), (LPARAM)TRUE);
        SendMsg(WM_SETTEXT, 0, (LPARAM)Str_00);
        SendItemMsg(IDC_HOWTOUSE, WM_SETTEXT, 0, (LPARAM)Str_01);
        SendItemMsg(IDC_ROLL, WM_SETTEXT, 0, (LPARAM)Str_02);
        SendItemMsg(IDOK, WM_SETTEXT, 0, (LPARAM)Str_03);
    }*/

//(解説:日本語フラグが立っていれば、ダイアログコントロールを日本語化します。そうでなければ(参考までに)英語化するのですが、デフォルトで設定済なのでお面とアウトしています。)

    //ステータスバー登録-SetHandle(hWnd))
    SBar.SetHandle(GetDlgItem(m_hWnd, IDC_STATUSBAR));
    //ステータスバー区画設定
    int sec[2] = {100, -1};
    SBar.SetSection(2, sec);
    //ステータスバー文字列設定
    SBar.SetText(0, "Dice Ver 1.0");
    if(g_JAPANESE)
        SBar.SetText(1, Str_14);
    else
        SBar.SetText(1, Str_04);

//(解説:ここも日本語フラグの場合は日本語メッセージを、そうでない場合には英語メッセージを表示します。)

    return TRUE;
}

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

    char *message, *title;
    if(g_JAPANESE) {
        message = Str_15;
        title = Str_16;
    }
    else {
        message = Str_05;
        title = Str_06;
    }

//(解説:ここも同じです。)

   if(MessageBox(m_hWnd, message, title, MB_YESNO | MB_ICONINFORMATION) == IDYES) {
        //処理をするとDestroyWindow、PostQuitMessageが呼ばれる
        EndModal(0);    //WinMainの正常終了は0Lを返す
        return TRUE;
    }
    else
        //そうでなければウィンドウではDefWindowProc関数をreturn、ダイアログではreturn FALSEとなる。
        return FALSE;
}

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

    CSTR str;
    char* title;
    if(g_JAPANESE) {
        str.FromFile("RurlesJP.txt");
        title = Str_17;
    }
    else {
        str.FromFile("RurlesEN.txt");
        title = Str_07;
    }

//(解説:ここも同じです。なおルールについてのテキストファイルを和英共に用意しています。)

    MessageBox(m_hWnd, str.ToChar(), title, MB_OK | MB_ICONINFORMATION);
    return TRUE;
}

bool CMyWnd::OnRoll() {

    int num[3];
    char buff[256];
    CSTR str;
    PlaySound("IDS_DICESOUND", m_hInstance, SND_RESOURCE | SND_ASYNC);

//(解説:きれいなチンチロの音がします。)

    for(int i = 0; i < 3; i++) {
        num[i] = SendItemMsg(IDC_D1 + i, DM_ROLL, 0, 0);
        if(g_JAPANESE)
            wsprintf(buff, Str_18, num[i], i);
        else
            wsprintf(buff, Str_08, num[i], i);

        str = str + buff + "\r\n";
        SendItemMsg(IDC_EDIT, WM_SETTEXT, 0, (LPARAM)str.ToChar());

//(解説:3つのサイコロについて、DM_ROLLメッセージで乱数による賽の目を出します。それに応じたメッセージをエディットボックスに表示します。)

    }
    m_Times--;        //賽を振る回数を減らす(目や役が出来なければ、3回迄賽を振れる)
/* 開発時のテスト用
    if(g_JAPANESE) 
        str = 
        "/////////////////////////////////////////////////////////////////\r\n"\
        "// チンチロリンの目、役の判定条件について\r\n"\
        "// 1 → 0 0 0 1\r\n"\
        "// 2 → 0 0 1 0\r\n"\
        "// 3 → 0 0 1 1\r\n"\
        "// 4 → 0 1 0 0\r\n"\
        "// 5 → 0 1 0 1\r\n"\
        "// 6 → 0 1 1 0\r\n"\
        "//「アラシ」\r\n"\
        "// num[0] | num[1] | num[2](== num[0] & num[1] & num[2])== num[0];\r\n"\
        "// 全ての要素のOR(またはAND)がいずれも要素である場合。\r\n"\
        "//「456」\r\n"\
        "// num[0] ^ num[1] ^ num[2] == 7 && num[0] & num[1] & num[2] == 4\r\n"\
        "// 各桁のビットが奇数個立っており、全ての要素が4以上である場合。\r\n"\
        "//「123」\r\n"\
        "// num[0] ^ num[1] ^ num[2] == 0 && num[0] | num[1] | num[2] == 3\r\n"\
        "// 各桁のビットが偶数個立っており、全ての要素が3以下である場合。\r\n"\
        "//「目」\r\n"\
        "// num[0] == num[1]) | (num[1] == num[2]) | (num[0] == num[2]\r\n"\
        "// 二つの要素のいずれかが等しい場合。\r\n"\
        "// なお、3つの要素の排他的論理和が最後の目となる。\r\n"\
        "/////////////////////////////////////////////////////////////////\r\n";
    else
        str = 
        "/////////////////////////////////////////////////////////////////\r\n"\
        "// Aoubt the conditions to determine hands of \"Chinchirorin\"\r\n"\
        "// 1 → 0 0 0 1\r\n"\
        "// 2 → 0 0 1 0\r\n"\
        "// 3 → 0 0 1 1\r\n"\
        "// 4 → 0 1 0 0\r\n"\
        "// 5 → 0 1 0 1\r\n"\
        "// 6 → 0 1 1 0\r\n"\
        "// <Storm>\r\n"\
        "// num[0] | num[1] | num[2](== num[0] & num[1] & num[2])== num[0];\r\n"\
        "// In case that all numbers' OR (or AND) is any of the numbers\r\n"\
        "// <456>\r\n"\
        "// num[0] ^ num[1] ^ num[2] == 7 && num[0] & num[1] & num[2] == 4\r\n"\
        "// In case that every digit has odd of \'1\' AND all numbers are more than 3.\r\n"\
        "// <123>\r\n"\
        "// num[0] ^ num[1] ^ num[2] == 0 && num[0] | num[1] | num[2] == 3\r\n"\
        "// Incase that every digit has even of \'1\' AND all numbers are less than 4.\r\n"\
        "// <Point>\r\n"\
        "// num[0] == num[1]) | (num[1] == num[2]) | (num[0] == num[2]\r\n"\
        "// In case that any of two numbers are same.\r\n"\
        "// In such case, the other number must be XOR of the three numbers.\r\n"\
        "/////////////////////////////////////////////////////////////////\r\n";
*/

//(解説:開発時のモニター用です。↓も同じ。)

    int Num_OR = num[0] | num[1] | num[2];
    int Num_AND = num[0] & num[1] & num[2];
    int Num_XOR = num[0] ^ num[1] ^ num[2];
    int Num_EQ = (num[0] == num[1]) | (num[1] == num[2]) | (num[0] == num[2]);

//(解説:。3つのサイコロのOR、AND、XORと二つが同じか否かのEQを使い、「目」と「役」を判断します。)

/* 開発時のテスト用
    wsprintf(buff, "num[0]= %d, num[1] = %d, num[2] = %d\r\n", num[0], num[1], num[2]);
    str = str + buff;
    wsprintf(buff, "num[0] | num[1] | num[2] = %d\r\n", Num_OR);
    str = str + buff;
    wsprintf(buff, "num[0] & num[1] & num[2] = %d\r\n", Num_AND);
    str = str + buff;
    wsprintf(buff, "num[0] ^ num[1] ^ num[2] = %d\r\n", Num_XOR);
    str = str + buff;
    wsprintf(buff, "(num[0] == num[1]) | (num[1] == num[2]) | (num[0] == num[2]) = %d\r\n", Num_EQ);
    str = str + buff;
    SendItemMsg(IDC_EDIT, WM_SETTEXT, 0, (LPARAM)str.ToChar());
*/

    if(Num_OR == Num_AND) {                    //アラシの場合
        if(g_JAPANESE)
            wsprintf(buff, "アラシが出ました!親の総取りです。\r\n");
        else
            wsprintf(buff, "Storm's here! You, Dealer's got everything!\r\n");

//(解説:バイリンガル処理です。)

        m_Times = 3;                        //初期化される

//(解説:役が付いたので振り数が3に戻ります。以下同じ。)

    }
    else if(Num_XOR == 7 && Num_AND == 4) {    //456の場合
        if(g_JAPANESE)
            wsprintf(buff, "456が出ました!親の総取りです。\r\n");
        else
            wsprintf(buff, "456's here! You, Dealer's got everything!\r\n");

        m_Times = 3;                        //初期化される
    }
    else if(Num_XOR == 0 && Num_OR == 3) {    //123の場合
        if(g_JAPANESE)
            wsprintf(buff, "なんと、123です!倍付け配等で親落ちです。\r\n");
        else
            wsprintf(buff, "My God, 123's here! Gotta pay doubled bets and quit Dealer!\r\n");

        m_Times = 0;                        //親落ち

//(解説:即負けなので残振り数はありません。)

    }
    else if(Num_EQ) {
        if(Num_XOR == 6) {                    //目「6」の場合
            if(g_JAPANESE)
                wsprintf(buff, "親に%dの目がつきました!親の総取りです。\r\n", Num_XOR);
            else
                wsprintf(buff, "You've got Point of %d! You, Dealer's got everything.\r\n", Num_XOR);

            m_Times = 3;                    //初期化される

        }
        else if(Num_XOR == 1) {               //目「1」の場合
            if(g_JAPANESE)
                wsprintf(buff, "なんと%dの目です!親の総付けです。\r\n", Num_XOR);
            else
                wsprintf(buff, "My God, You've got Point of %d! Gotta pay everyone.\r\n", Num_XOR);

            m_Times = 0;                    //親落ち

//(解説:「目」が出なかった場合です。)

        }
        else {
            if(g_JAPANESE)
                wsprintf(buff, "親に%dの目がつきました。\r\n", Num_XOR);
            else
                wsprintf(buff, "You've got Point of %d.\r\n", Num_XOR);

            m_Times = 3;                    //初期化される
        }
    }
    else
        *buff = NULL;
    //目または役が出た場合、ステータスバーに表示
    SBar.SetText(1, buff);
    //最後に賽を振れる残回数を表示
    if(g_JAPANESE) {
        wsprintf(buff, Str_19, m_Times);
        str = str + buff + "\r\n";
    }
    else {
        wsprintf(buff, Str_09, m_Times);
        str = str + buff + "\r\n";
    }

    SendItemMsg(IDC_EDIT, WM_SETTEXT, 0, (LPARAM)str.ToChar());
    //終了チェック
    if(!m_Times) {
        if(g_JAPANESE) {
            if(MessageBox(m_hWnd, "あなたの負けです。まだ続けますか?", "勝負の終了",
                            MB_YESNO | MB_ICONQUESTION) == IDYES)
                m_Times = 3;
            else
                OnIdok();
        }
        else {
            if(MessageBox(m_hWnd, "You lost. Wanna continue yet?", "Game over",
                            MB_YESNO | MB_ICONQUESTION) == IDYES)
                m_Times = 3;
            else
                OnIdok();
        }
    }

//(解説:もう配列が残っていなかったのでここから定数表示です。)

    return TRUE;
}

bool CMyWnd::OnIdok() {

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

bool CMyWnd::OnDice1(WPARAM wParam) {

    char buff[64], *cp;
    if(g_JAPANESE)
        cp = "%d番の賽が振られ、賽の目は%dでした。";
    else
        cp = "The dice of %d was rolled and made %d.";

    wsprintf(buff, cp, LOWORD(wParam) - IDC_D1 + 1, HIWORD(wParam));
    SBar.SetText(1, buff);
    return TRUE;
}

//(解説:後で追加した賽単独の通知の処理です。もう配列が残っていなかったのでここも定数表示です。以下同じ。)


bool CMyWnd::OnDice2(WPARAM wParam) {

    char buff[64], *cp;
    if(g_JAPANESE)
        cp = "%d番の賽が振られ、賽の目は%dでした。";
    else
        cp = "The dice of %d was rolled and made %d.";

    wsprintf(buff, cp, LOWORD(wParam) - IDC_D1 + 1, HIWORD(wParam));
    SBar.SetText(1, buff);
    return TRUE;
}

bool CMyWnd::OnDice3(WPARAM wParam) {

    char buff[64], *cp;
    if(g_JAPANESE)
        cp = "%d番の賽が振られ、賽の目は%dでした。";
    else
        cp = "The dice of %d was rolled and made %d.";

    wsprintf(buff, cp, LOWORD(wParam) - IDC_D1 + 1, HIWORD(wParam));
    SBar.SetText(1, buff);
    return TRUE;
}

///////////////////////////////
//ユーザーダイアログの関数定義
//コントロール関数
///////////////////////////////

 

いかがでしたか?結構その気にさせて、遊ばせてくれますね、チンチロは。