昨日は色々と悩みがあって、疲れました。

悩みというのは「メナスの盤データにアクセスをさせるべきか、否か」であり、今頃になって根幹仕様がぐらついてしまいました。

その理由はメナスの盤データを表示するメンバー関数はありますが、メナスの盤の「値渡し」で処理(注)した盤データを表示する関数が無いことです。といってメナスの盤データ(原本)を勝手に処理することもできず、どうしようかと悩んだのでした。

注:盤面を回転させたり、反転させたり、先手、後手を交換したりする処理です。詳しくは後日説明します。

 

結局、セキュリティ上メナスの盤データはprotectedにし、手を打つ関数(一度打ったところには打てない為、盤データの書き換えは絶対的にできない)のみしかアクセスできないようにしました。

 

とうことで、データ形とCBOARDクラスの説明に入ります。(背景、経緯、仕様等は既に【アルゴリズム】のブログで述べているので割愛します。)

 

1.データ形

盤のデータと付帯データを構造体として定義しています。

 

//定数定義(先手(1)、後手(2)、未打(0))
#define        FIRST    1
#define        SECOND    2    //-1は、評価で1 x -1 x 1 = -1や-1 x 1 x -1 = 1となるので止めた
#define        VACANT    0


////////////////////////////////////
//BOARD Structure for use of CBOARD
////////////////////////////////////
typedef struct _board {

    char m_Pos[9];
    //    -------        盤面はm_Pos[0]-[8]のchar配列を使う
    //    |0|1|2|        これがMENACEのビーズに相当
    //    -------
    //    |3|4|5|
    //    -------
    //    |6|7|8|
    //    -------
    int m_LastMove;    //試合中は最終手(0 - 8)、試合後DB登録時は
} BOARD, *PBOARD;    //評価の為に勝者データ(先手(1)、後手(2))

盤のデータはm_Posという,9Bのchar配列変数です。当初は〇×のデータを2bitで18ビット、付帯データを入れても3バイト以下で押さえようかと思いましたが、それは悲しい8bit、主記憶64KBの時代のトラウマで、現在の32bitコンピューター(最小データ単位はchar)では却って速度低下要因となることから止めました。

m_LastMoveという変数は対戦中は何手目を打ったか、を記録(注)しますが、対戦の勝敗が付いた後は勝者データを入れます。(本来は勝者データを別に持っていたのですが、両データは重なり合わないので、サイズ縮小目的から共用にしました。

注:何手目かを記録するのは、盤面のデータは現在のデータでなく、「一手前のデータ」なので元に戻す(PushBack関数に担当させています)ためです。

 

////////////////////////
//CMBOARDクラスヘッダー
//(盤面データのクラス)
////////////////////////
class CBOARD {


protected:                                        //CBOARDと承継したCMENACEは利用できる
    //変数
    BOARD m_Board;                                //試合用盤

//(解説:これが↑で述べていた、クラス外部からのアクセスをどうするか、問題の部分です。)

public:
    //関数
    CBOARD();                                    //コンストラクター(初期化のみ)
    void Init();                                //初期化(再ゲーム時に必要)
    BOARD GetBoard() {return m_Board;}            //盤面データ(m_Board)のデータを渡す

//(解説:この関数は盤データを渡しますが、ポインターや参照渡しではないので、値(データのコピー)を渡しているだけです。

    bool MakeMove(int, char);                    //手を打つ
//(解説:この関数は盤データにアクセスできます。しかし、一度打った場所に打ち直すことはエラーとなり、禁じられています。)

    int* CheckBoard();                            //盤面を調べる(戻り値は空き位置の配列とその数)
//(解説:この関数は「打つことができる場所を配列で渡します。配列の要素の個数は配列の最後に入っています。)

    bool RandomMove(char);                        //乱数で手を打つ
//(解説:この関数も盤データにアクセスできます。しかし、一度打った場所に打ち直すことはエラーとなり、禁じられています。)

    void RecMove(int m) {m_Board.m_LastMove = m;}    //手を記録する
//(解説:この関数がm_LastMoveに対戦中の打った場所を記録させます。)

    int IsOver();                                //未了-0、縦横斜めに3つ並んだ-1|2、引き分け-3

//(解説:勝敗評価関数です。なお、CMENACEになるとこの関数の他、MENACEが「投了」して負けることがあります。)

    void PushBack(int);                            //最終手ひとつ前に戻してm_Board.m_LastMoveに勝者を記録する

//(解説:対戦後、一手戻して「どういう打ち手が原因で負けたかを記録します。)

    int WhoWon() {return m_Board.m_LastMove;}    //PushBack()後の勝者確認用
    virtual void ShowBoard();                    //盤面表示の仮想関数

//(解説:この関数はCUI用で、ウィンドウズプログラムになった時には使いません。)

};

//コンストラクター(初期化のみ)
CBOARD::CBOARD() {

    srand(time(NULL));                //乱数の初期化
    Init();
}

//(解説:乱数を初期化し、次のInit()関数でメンバー変数を初期化します。)


//初期化(再ゲームに必要)
void CBOARD::Init() {

    for(int i = 0; i < 9; i++)
        m_Board.m_Pos[i] = 0;        //m_Board.m_Pos配列を0初期化
    m_Board.m_LastMove = 0;            //m_Winの初期化
}

//(解説:コメントにある通り、以前はm_Winという変数に勝者データを入れていました。)


//手を打つ
bool CBOARD::MakeMove(int i, char hand) {

    if(m_Board.m_Pos[i] != VACANT)
        return false;
    else {
        m_Board.m_Pos[i] = hand;    //手を打つ
        RecMove(i);                    //打手を記録
        return true;
    }
}

//(解説:既に打たれている場所には打つことができません。)

//盤面を調べる(戻り値は空き位置の配列とその数)
int* CBOARD::CheckBoard() {

    static int pos[10];
    pos[9] = 0;                        //pos[9]に残空き位置数を入れる
    for(int j = 0; j < 9; j++) {
        if(m_Board.m_Pos[j] == VACANT) {
            pos[pos[9]] = j;
            pos[9]++;
        }
    }
    if(!pos[9])
        return NULL;                //打てる場所が無い(pos「9」== 0)
    else
        return pos;                    //打てる場所の配列
}

//(解説:最大9の場所データを渡します。10個目の配列にデータ個数を入れています。)

//乱数で手を打つ(打てない場合、不可フラグをたて、falseを返す)
bool CBOARD::RandomMove(char hand) {

    int* cp  = CheckBoard();
    if(!cp[9])                        //cp[0] - cp[8]迄が空き位置の添字、cp[9]が残空き位置数
        return false;
    else {
        int i = cp[rand() % cp[9]];
        MakeMove(i, hand);
        RecMove(i);                    //打手を記録
        return true;
    }
}

//(解説:この関数も盤データを変えますが、矢張り打った場所には打てないので修正は不能です。)
 

int CBOARD::IsOver() {

    int res;            //勝負の進行状態を表す戻り値
    if(!CheckBoard())    //残空き位置無し(全て埋まって勝敗が決した時は書き直される)
        res = 3;        //引き分け状態が初期値
    else
        res = 0;        //空き位置があれば未了状態が初期値
    int chk;            //縦横斜め3つの手の積(先手なら1、後手なら8になる)
    if((chk = m_Board.m_Pos[0] * m_Board.m_Pos[1] * m_Board.m_Pos[2]) == 1)    //横第1行
        res = FIRST;    //先手勝利
    if(chk == 8)
        res = SECOND;    //後手勝利
    if((chk = m_Board.m_Pos[3] * m_Board.m_Pos[4] * m_Board.m_Pos[5]) == 1)    //横第2行
        res = FIRST;    //先手勝利
    if(chk == 8)
        res = SECOND;    //後手勝利
    if((chk = m_Board.m_Pos[6] * m_Board.m_Pos[7] * m_Board.m_Pos[8]) == 1)    //横第3行
        res = FIRST;    //先手勝利
    if(chk == 8)
        res = SECOND;    //後手勝利
    if((chk = m_Board.m_Pos[0] * m_Board.m_Pos[3] * m_Board.m_Pos[6]) == 1)    //縦第1列
        res = FIRST;    //先手勝利
    if(chk == 8)
        res = SECOND;    //後手勝利
    if((chk = m_Board.m_Pos[1] * m_Board.m_Pos[4] * m_Board.m_Pos[7]) == 1)    //縦第2列
        res = FIRST;    //先手勝利
    if(chk == 8)
        res = SECOND;    //後手勝利
    if((chk = m_Board.m_Pos[2] * m_Board.m_Pos[5] * m_Board.m_Pos[8]) == 1)    //縦第3列
        res = FIRST;    //先手勝利
    if(chk == 8)
        res = SECOND;    //後手勝利
    if((chk = m_Board.m_Pos[0] * m_Board.m_Pos[4] * m_Board.m_Pos[8]) == 1)    //斜め第1列
        res = FIRST;    //先手勝利
    if(chk == 8)
        res = SECOND;    //後手勝利
    if((chk = m_Board.m_Pos[2] * m_Board.m_Pos[4] * m_Board.m_Pos[6]) == 1)    //斜め第2列
        res = FIRST;    //先手勝利
    if(chk == 8)
        res = SECOND;    //後手勝利
    //未了、引き分けでなければ一手戻して勝者を記録する(DB登録用)
    if(res == FIRST || res == SECOND)
        PushBack(res);
    return res;
}

//(解説:長たらしく書かれていますが、空き位置があるか、縦横斜めに1または2が3つ並んでいるかを判断しています。)

//最終手ひとつ前に戻してm_Board.m_LastMoveに勝者を記録する(DB登録用)
void CBOARD::PushBack(int winner) {

    m_Board.m_Pos[m_Board.m_LastMove] = 0;
    m_Board.m_LastMove = winner;
}

//(解説:これが勝敗決定後の処理です。最後の手を「空き」に戻し、勝者を記録します。)

//盤面表示関数(CUI版-仮想関数で承継時オーバーライド可能)
void CBOARD::ShowBoard() {

    char c;
    cout << "-------" << endl;
    for(int i = 0; i < 9; i++) {
        switch(m_Board.m_Pos[i]) {
        case FIRST:
            c = 'O';
            break;
        case SECOND:
            c = 'X';
            break;
        case VACANT:
            c = ' ';
            break;
        }
        cout << "|" << c;
        if((i % 3) == 2)
            cout << "|" << endl << "-------" << endl;
    }
}

 

この段階では、コンピューターは単に空いている場所にランダム(乱数)に手を打つだけです。とはいえ、キチンと動作を確認するためのテストプログラムを載せておきます。コンパイルは将来vectorを利用するのでBCC55ではエラーが出るため、BCC102で大なってください。そのためのバッチプログラムも載せます。

 

【テストプログラム】

///////////////////////////////
// Console program for CBOARD
///////////////////////////////
#include    <conio.h>    //getch()使用の為
#include    <stdlib>    //srand()、rand()使用の為
#include    <iostream>

using namespace std;

#include    "..\CBOARD.h"

int main(int argc, char **argv) {

    CBOARD board;
    cout << "CBOARDクラスのデータ(char x 9 + int)サイズ = " << sizeof(board)  << "バイト" << endl;
    cout << "BD構造体のデータ(char x 9 + int)サイズ = " << sizeof(BOARD)  << "バイト" << endl << endl;
    cout << "先手(1)、後手(2)または機械対戦(3)を選択してください" <<endl;
    int you;
    while(you < 1 || you > 3) {
        cin >> you;
    }
    cout << "<<盤面座標>>\r\n-------\r\n|0|1|2|\r\n-------\r\n|3|4|5|\r\n-------\r\n|6|7|8|\r\n-------\r\n>>>>>><<<<<<" << endl;
    int times = 0;
    int res = 0;    //未了0、先手勝利1、後手勝利2、引き分け3
    board.ShowBoard();
    do {
        switch(you) {
        case 1:    //先手
            if(!(times % 2)) {    //先手打
                cout << "先手は打つ場所(0-8)を選んでください" << endl;
                int move = -1;
                while(move < 0 || move > 8) {
                    cin >> move;
                    if(!board.MakeMove(move, you)) {
                        cout << "そこは既に打たれています" << endl;
                        move = -1;
                    }
                }
            }    
            else            //後手打
                board.RandomMove(2);
            board.ShowBoard();
            break;
        case 2:    //後手
            if(!(times % 2))    //先手打
                board.RandomMove(1);
            else {            //後手打
                board.ShowBoard();
                cout << "後手は打つ場所(0-8)を選んでください" << endl;
                int move = -1;
                while(move < 0 || move > 8) {
                    cin >> move;
                    if(!board.MakeMove(move, you)) {
                        cout << "そこは既に打たれています" << endl;
                        move = -1;
                    }
                }
            }    
            board.ShowBoard();
            break;
        case 3:    //機械対戦
            if(!(times % 2))    //先手打
                board.RandomMove(1);
            else {            //後手打
                board.RandomMove(2);
            }    
            board.ShowBoard();
            break;
        }
        times++;
        if(times > 4)
            res = board.IsOver();
    } while(!res);
    switch(res) {
    case 1:
        cout << ">>>先手の勝ち-終了一手前盤面<<<" << endl;
        break;
    case 2:
        cout << ">>>後手の勝ち-終了一手前盤面<<<" << endl;
        break;
    case 3:
        cout << ">>>引き分け-終了時盤面<<<" << endl;
        break;
    }
    board.ShowBoard();
    cout << "(m_LastMove = " << board.WhoWon() << ")" << endl << endl;
    //Pause
    getch();

    return 0;
}

 

【バッチプログラム例】

"C:\Borland\BCC102\bin\bcc32c.exe" "C:\Users\(パス)\MENACE\CBOARD-Test\BOARD.cpp" -tC -w
del "C:\Users\(パス)\MENACE\CBOARD-Test\BOARD.tds"
pause

 

 

 

粗い仕様に基づいてCUIベースのMENACEをクラスオブジェクトとして纏めてから、それをWin32ベースに移植しよう、というのが方針で、現在もそれは変わっておりません。

 

最初の予告を出してから、データ、クラスオブジェクト、プログラム(Win32を想定)にそれぞれ「何を、どこまでさせるか」考えながら、試行錯誤でデータ形、メンバー関数、インターフェース等をスクラップビルド(アイデアに基づいてプロトタイプを作り、矢張りだめだ、いやもっといい方法がある、ということで仕様変更を行う、という私流の煮詰め方)で作り込み、最終的な姿が見えてきたように思えるところまで来ました。

 

データ形だけでも、最初はそれほど構造体とサイズの違いが無いのでクラスでつくっちゃおうと思いましたが、途中から以下に述べる三層構造の在り様を見直し、矢張りデータだけの構造体に本日変更(朝3時に目が覚めて、考えに思いついた)しましたし、クラスオブジェクトでゲームを完結させる、という考えも改めてウィンドウズ(現在はCUI)プログラムに任せる、ということにしました。

 

     ウィンドウズ(現在はCUI)プログラム→ゲームの管理、人とのインターフェース

              /\

       メナスクラス(CMENACE)→下級の盤クラスを承継し、盤面の記憶と検索、判断機能を付加

           /      \

       盤クラス(CBOARD)→盤データに関わる基本的なゲーム進行機能を実装

        /            \

      盤データ(BOARD構造体)→盤データとCBOARDの為の一時データや評価用データを収納

 

①は構造体メンバーを付けたり、外したり、内容を変えたりしましたが、(テストプログラムを利用して)②と共にこんな感じかな、という安定したところまで来ています。

現在は③を頑張っている最中ですが、盤面データのDBはSTLのvectorを取り敢えず使い、完成して検索速度が遅ければlistで登録段階でソートしておいて、バイナリーサーチができないか、考えています。
 

今日はCUIに飽きたので、BCCFormとBCCSkeltonでGUIプログラムのスケルトンを作りました。我ながら、これは非常に開発が効率的で、

まず〇、×イメージやツールバーボタンをEZImageでつくり、

すぐにモードレスダイアログベースのスケルトンができました。(自分でも驚いた!のですが、EZImageは本来16x16と32x32のイメージしか扱えない筈でしたが、64x64の〇×イメージを読み込んだら上記の通りとなりました。ステータスバーの位置も「63, 63」と出ています。)

後はじっくりと上記「内臓」を作って中に入れるだけです。

(イメージはダイアログベースのMENACEスケルトンです。)

 

派手な表現やAVインターフェースは新しい開発言語に任せて、次のネタはよりアルゴリズムに着目したゲーム、その名もMENACEを考えています。

 

MENACEというと、英語で「脅迫、威嚇、脅し、危険なもの、やっかいな人、困り者」という名詞があり、アクションゲームやサバイバルゲームを想像される方もあると思いますが、今考えているのは全く別物です。寧ろSampleBCCSkeltonフォールダーに入っているLifeGame、LightCycleやMazeなどと同様の(人によっては面白くないと敬遠されるような)アルゴリズムを楽しむゲームです。

 

【私とMENACEの馴れ初め】

実は私がこのMENACEに出会ったのは、1986年当時のASCIIというPC雑誌に載った「マッチ箱計算機、MENACE」という記事でした。記事はそのアルゴリズムとC(だったと思います)のサンプルプログラムがあり、当時の開発環境(BDS-C)の関連からスクラップで残していたものを参考にシンガポール時代にBCCSkeltonでウィンドウアプリを開発しました。その後ActiveBasicにも移植してBorland C++のネイティブコードの速さに驚いたものです。

しかし、私のソフトウェア資産は2016年頃の機種入れ替えで雲散霧消した際、BCC版もActiveBasic版もウィンドウズMENACEは消えてしまいました。

その後、ブランクが20年近く、プログラムやアルゴリズムを思い出すこともできず、WEBで当時の記事を探したのですが、余りに旧くて見当たらず、関連記事も(末尾参照)多くはありません。
 

【MENACEとは何か】

MENACEとは"Matchbox Educable Noughts and Crosses Engine"の略で、要すれば、俗に言うAIのはしりとなる機械学習型〇×ゲーム("Noughts and crosses"とか"tic-tac-toe"とか呼びます)です。しかし、開発当初は全くコンピューターを使っておらず、マッチ箱を使って対戦経験により学習を行うところが特徴でした。

PCゲーム化した場合、現象的にみると、最初は乱数で対戦していたコンピューターが対戦経験をDB化し、負ける手を打たないような判断アルゴリズムのプログラムになります。(結局最後はPCが強くなって、引き分けばかりで面白くなないかもしれませんがね。)

 

【今考えていること】

どのようにして機械学習するのかは末尾の資料に譲り、現在「なるべく忠実にマッチ箱を使ったアルゴリズムと手順をもとに、白紙からMENACEを設計」しようとしています。まぁ、現在CUIのC++プログラムで試行錯誤中ですが、いずれBCCSkeltonでウィンドウズプログラムにして公表しましょう。


【参考WEBサイト】

<和文>

 

<英文>

 

 

プログラミングとは全く無縁のネット探しをしていて、以下のサイトに偶然行きつきました。

 

 

記事の中にある、

「初代のスーパーマリオの全ゲーム容量は40KB、初代ドラゴンクエストの全容量は64KB」

「7行テトリス」

などが、昨日(一昨日ー注)「正直、めげ」ていた私に勇気を与えてくれます。やはり」C++はアルゴリズムで勝負か?

 

注:昨日アップを忘れていました!

いえいえ、COMが大変なのでめげるのではありません。インターフェースならスタンドアロンのDLL等は見込み薄で、もうWin32APIやC++の時代ではない、というヤレヤレです。

 

【Windowsに登録されたサービス】

大体じぶんのPC にどんな機能が登録されているのか、まずはそれを一覧してみて考えようということで、CLSIDとProgIDの一覧を取ってみました。

(1)CLSID一覧

C:\Windows\System32のreg.exeというコマンドを使い、コマンドラインから

"reg query HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\ > CLSID.txt"

といれて一覧を取得します。

KEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\CLSIDフォールダーの中のCLSIDを取得しましたが、正直こんな奴ら(「{0000002F-0000-0000-C000-000000000046}」)の一覧ではチンプンカンプンです。
 

(2)ProgID一覧

同じくコマンドラインから

"reg query HKEY_LOCAL_MACHINE\Software\Classes\ > ProgID.txt"

といれて一覧を取得します。

今度はファイル拡張子(例:HKEY_LOCAL_MACHINE\Software\Classes\.bmp)や実行ファイル(例:HKEY_LOCAL_MACHINE\Software\Classes\Calc.Application)などがあり、まだましになりましたが、矢張り「これだっ!」とDLLを見つけるなどということは難しいかもしれませんね。

 

ということで、このアプローチは続けるも、少し時間がかかることになりそうです。

 

【動画サービス】

前回のブログでFlashWaveが2019年で撤退した、ということを知ったと書きました。その理由も「技術的な陳腐化」によるものである、ということも知りました。(20年前はFlashWaveはバリバリ現役でしたがね。)であれば、その「HTML5 Canvas や Web GL」などというものがどんなものか、ググって確認しました。

経緯は2010年頃まで急速に普及した携行端末がFlash非対応を貫き、当時のSteven Jobs CEOがHTML5を支持したことが流れを変えることになった原因のようです。特にスマートフォンはFlashからHTML5への転換が進み、Adobeは2011年11月にモバイルブラウザー用のFlash Playerの開発中止を発表したようです

じゃ、そのHTML5ってどんなものかというとこんなだそうです。特にビデオ、オーディオ再生のAPIを持っていたことから、↓のような短いコードでこんなことができちゃいます、(テキストファイルにコペピして".html"拡張子で実行できます。)

//原典は↓

//https://developers.google.com/youtube/iframe_api_reference?hl=ja
//

<!DOCTYPE html>
<html>
  <body>
    <!-- 1. The <iframe> (and video player) will replace this <div> tag. -->
    <div id="player"></div>

    <script>
      // 2. This code loads the IFrame Player API code asynchronously.
      var tag = document.createElement('script');

      tag.src = "https://www.youtube.com/iframe_api";
      var firstScriptTag = document.getElementsByTagName('script')[0];
      firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

      // 3. This function creates an <iframe> (and YouTube player)
      //    after the API code downloads.
      var player;
      function onYouTubeIframeAPIReady() {
        player = new YT.Player('player', {
          height: '360',
          width: '640',
          videoId: 'M7lc1UVf-VE',
          events: {
            'onReady': onPlayerReady,
            'onStateChange': onPlayerStateChange
          }
        });
      }

      // 4. The API will call this function when the video player is ready.
      function onPlayerReady(event) {
        event.target.playVideo();
      }

      // 5. The API calls this function when the player's state changes.
      //    The function indicates that when playing a video (state=1),
      //    the player should play for six seconds and then stop.
      var done = false;
      function onPlayerStateChange(event) {
        if (event.data == YT.PlayerState.PLAYING && !done) {
          setTimeout(stopVideo, 6000);
          done = true;
        }
      }
      function stopVideo() {
        player.stopVideo();
      }
    </script>
  </body>
</html>

ほんーっと、いやになっちゃう。

何これ?これをC++で、クラサバのWindows OSで、SDIスケルトンにコントロール貼ってネットワークにつないで、なんてやっていたらバカでかいファイルになりますよね?それがちょちょいっとできちゃう。時代が変わったなー、としみじみ思っちゃいました。

 

【ということはよ?結論なんだけど】

C++プログラミングでCOMが扱えるようになった、と浮かれて、新しいサービスを探していましたが、ShellとTextToSpeech以外に迫力のあるものは乏しく、且つこういうAV系やインターフェース的な世界はWEBやHTML、XAMLなどに任せて、C++は清く正しい数値計算やデータ処理に徹した方が良い、それが身の為ということかなと自虐的に感じました。

 

まぁ、当分はこのような駄文を書きながら少し考えましょう。
 

ps. と締めくくろうとしたら、FlashWaveを駆逐したHTML5がもう既にお釈迦でHTML Living Standardにとってかわられたそーな。なんまいだぶ、なんまいだぶ。

どうしてHTML5が廃止されたのか

 

本日何気なくVectorをのぞいてみたら、「C言語」ジャンルの人気順でBCCForm and BCCSkeltonが第3位になっていました。それ自体は何の感慨もないのですが、

問題は第3位が「2003年の旧バージョンのBCCForm and BCCSkelton」になっていることです。

【旧2002年版】

BCCForm and BCCSkelton 2.41R1.5 または

 

 

今回(2021年)にアップしたのは次の新版です。

【新2021年版】

BCCForm and BCCSkelton または

 

どうか、お間違いのないようにダウンロードしてください。

 

追補:どうしてこんなことになったかというと、20年前にアップした際のパスワードを失念し、更新ができなくなったことから旧作者名"ysama""ysama2021"にして新規でアップしたからです。どうかお許しください。

 

 

 

 

2000年リリースのbcc32 ver 5.5(BCC55)のコンパイラ―オプションが、Clang対応のbcc32c ver 7.3(BCC102-注)で有効ではない事実について既に「【BCC】C++コンパイラーの謎」シリーズで触れてきたところです。

注:"2012-2017"と表示されますが、リリース2018年頃と思われます。

 

Embarcadero自体は次のように公表しています。

[C++コンパイル]オプション

[C++ コンパイラ|互換性]

[C++ コンパイラ|最適化]

 

また、bcc32c自体も、"-h"オプションを与えるとヘルプが表示されます。(前に紹介したCommand.exeを使ってみてください。)

 

 

このうちヘルプ表示を独自に翻訳された方のサイトがあり、その成果物がpdfで公開されていますので紹介します。筆者は単に翻訳されたのみならず、独自に「BCC32Cでサポートされないオプション」という項目を付加されており、とても有用です。ただ一点、"-W (ターゲットは Windows アプリケーション)"が無印でサポートされている様に見えますが、小生の環境では、

とbcc32cは通るのですが、Turbo linkerで

Turbo Incremental Link 6.90 Copyright (c) 1997-2017 Embarcadero Technologies, Inc.
Error: Unresolved external '_main' referenced from C:\BORLAND\BCC102\LIB\WIN32C\DEBUG\C0X32.OBJ
Error: Unable to perform link
bcc32c.exe: error: linker command failed with exit code 2 (use -Xdriver -v to see invocation)

とウィンドウアプリケーションとして認識されずエラーになります。"-tW"(コンソールアプリケーションは"-tC")でコンパイルした方が無難だと思います。(以下は"-tW"で正常にリンクされました。)

 

bcc32cでCOM関係のコンパイルが可能になったので、COMの汎用サービスでいいものは無いかとここらへんなどを覗いたが、ShellやTextToSpeech(注)のような素人にインパクトのあるサービスが見当たらない。

注:昔BCB5.0(英語版)をいじっていて、ActiveXからコンポーネントをインポートするメニューでこれを見つけ、きれいな英語の発音とそれに合わせて動く口(小生のTextToSpeechプログラムのICONイメージになっています)の印象が強烈でした。

 

あれこれ見て回るうちにAdobeのFlashWaveがCOMコンポーネントとして使え、VC++などでインポートするサンプルが見当たったのでこれをやろうかな、と思ったのですが、ご本家でこんなことを言っていました。あーあ。

 

しかし、終了理由のところを見ると、

 

アドビが Shockwave Player の提供を終了するのはなぜですか?

テクノロジの進化とモバイルデバイスの普及により、インタラクティブコンテンツは HTML5 Canvas や Web GL などのプラットフォームに移行しています。このため、Shockwave が利用される機会が減少しています。

 

HTML5 Canvas や Web GL???もう時代遅れなんだよねー、という感じをとても強く受けました。

 

まぁ、懐メロ的に素人プログラミングで楽しめればよいんで、ゆっくり、じっくりやりましょう。

 

新しいbcc32cでBCCSkeltonが使えるようになったことに何の意味があるのでしょうか?コンパイルすると新しいコンパイラーなのでコードは洗練されているのでしょうが、動作は変わらないし、サイズは明らかに大きくなってしまいます。

 

BCC201が使える本当の恩恵、便益は、2000年に公開されたBCC5.5(bcc32.exe)では古すぎてコンパイルできないものができるようになり、ウィンドウズプログラミングの幅が広がることです。

 

このことをファイルを開くダイアログをCOMで動かす簡単なサンプルプログラムで示します。

 

【サンプルプログラム】

#include <windows.h>
#include <shobjidl.h>

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow) {

    //FileOpenDialogオブジェクトはIFileDialogで識別
    IFileDialog *pFileDialog;
    HRESULT     hr;
    //オブジェクトを操作するスレッドの数だけCoInitializeを呼び出す必要がある
    CoInitialize(NULL);
    //CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_IFileDialog, (void **)&pFileDialog);
    hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pFileDialog));
    //第1引数はFileOpenDialogオブジェクトを識別するID
    //第3引数がCLSCTX_INPROC_SERVERで、このオブジェクトを実装するDLLがロードされる
    //IID_PPV_ARGSというマクロは引数に指定された変数から適切なIIDを割り出す
    // IID_PPV_ARGSマクロは、割り出したIIDと変数のアドレスをカンマで区切って返す
    if (FAILED(hr)) {
        CoUninitialize();
        MessageBox(NULL, "CoCreateInstanceに失敗しました", "エラー", MB_OK | MB_ICONERROR);
        return 0L;
    }
    pFileDialog->Show(NULL);
    pFileDialog->Release();
    //COMの使用で生じた全リソース(メモリやDLL、RPC接続など)をCoUninitializeで開放
    CoUninitialize();

    return 0L;
}

 

これを(makeを使い)bcc32でコンパイルすると次のエラーが出ます。

【bcc32】

MAKE Version 5.2  Copyright (c) 1987, 2000 Borland
    echo -W -6 -O1 -w- -AT -ps -H- -k -b -nRelease -IC:\Borland\BCCForm -c > Release\FileOpenDialog.rsp
    echo "C:\Users\(パス)\FileOpenDialog\FileOpenDialog.cpp" >> Release\FileOpenDialog.rsp
    bcc32 @Release\FileOpenDialog.rsp
Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland
C:\Users\(パス)\FileOpenDialog\FileOpenDialog.cpp:
エラー E2257 C:\Borland\bcc55\Include\shtypes.h 156: , が必要
エラー E2257 C:\Borland\bcc55\Include\shtypes.h 469: , が必要
エラー E2257 C:\Borland\bcc55\Include\shtypes.h 471: , が必要
エラー E2293 C:\Borland\bcc55\Include\windows\sdk\StructuredQueryCondition.h 131: ) が必要
エラー E2451 C:\Borland\bcc55\Include\windows\sdk\StructuredQueryCondition.h 205: 未定義のシンボル __RPC__in
エラー E2293 C:\Borland\bcc55\Include\windows\sdk\StructuredQueryCondition.h 205: ) が必要
エラー E2293 C:\Borland\bcc55\Include\windows\sdk\StructuredQueryCondition.h 239: ) が必要
エラー E2293 C:\Borland\bcc55\Include\windows\sdk\StructuredQueryCondition.h 242: ) が必要
エラー E2293 C:\Borland\bcc55\Include\windows\sdk\StructuredQueryCondition.h 247: ) が必要
エラー E2293 C:\Borland\bcc55\Include\windows\sdk\StructuredQueryCondition.h 254: ) が必要
エラー E2293 C:\Borland\bcc55\Include\windows\sdk\StructuredQueryCondition.h 257: ) が必要
エラー E2293 C:\Borland\bcc55\Include\windows\sdk\StructuredQueryCondition.h 261: ) が必要
エラー E2293 C:\Borland\bcc55\Include\windows\sdk\StructuredQueryCondition.h 268: ) が必要
エラー E2293 C:\Borland\bcc55\Include\windows\sdk\StructuredQueryCondition.h 419: ) が必要
エラー E2293 C:\Borland\bcc55\Include\windows\sdk\StructuredQueryCondition.h 433: ) が必要
エラー E2293 C:\Borland\bcc55\Include\windows\sdk\StructuredQueryCondition.h 466: ) が必要
エラー E2293 C:\Borland\bcc55\Include\windows\sdk\StructuredQueryCondition.h 470: ) が必要
エラー E2293 C:\Borland\bcc55\Include\windows\sdk\StructuredQueryCondition.h 645: ) が必要
エラー E2356 C:\Borland\bcc55\Include\windows\sdk\StructuredQueryCondition.h 674: '__stdcall BSTR_UserSize(unsigned long *,unsigned long,wchar_t * *)' の再宣言で型が一致していない
エラー E2344 C:\Borland\bcc55\Include\ocidl.h 8148: 一つ前の '__stdcall BSTR_UserSize(unsigned long *,unsigned long,wchar_t * *)' の定義位置
エラー E2063 C:\Borland\bcc55\Include\windows\sdk\StructuredQueryCondition.h 674: 不正な初期化
エラー E2293 C:\Borland\bcc55\Include\windows\sdk\StructuredQueryCondition.h 674: ) が必要
エラー E2356 C:\Borland\bcc55\Include\windows\sdk\StructuredQueryCondition.h 675: '__stdcall BSTR_UserMarshal(unsigned long *,unsigned char *,wchar_t * *)' の再宣言で型が一致していない
エラー E2344 C:\Borland\bcc55\Include\ocidl.h 8149: 一つ前の '__stdcall BSTR_UserMarshal(unsigned long *,unsigned char *,wchar_t * *)' の定義位置
エラー E2063 C:\Borland\bcc55\Include\windows\sdk\StructuredQueryCondition.h 675: 不正な初期化
エラー E2228 C:\Borland\bcc55\Include\windows\sdk\StructuredQueryCondition.h 675: エラーあるいは警告が多すぎる
*** 26 errors in Compile ***
** error 1 ** deleting Release\FileOpenDialog.exe
Build End !! (Elapsed time 0:01.000)
 

しかし、これをbcc32c(およびBCC102のコマンドツール)でコンパイルすると楽々と成功します。(以下はバッチファイルでコンパイルしたものですが、オプションを"-tW -w"にしてBCCMakerでコンパイルしても変わりません。)

【bcc32c】

Embarcadero C++ 7.30 for Win32 Copyright (c) 2012-2017 Embarcadero Technologies, Inc.
C:\Users\(パス)\FileOpenDialog\FileOpenDialog.cpp:
Turbo Incremental Link 6.90 Copyright (c) 1997-2017 Embarcadero Technologies, Inc.
 

この20年間のライブラリー(libフォールダー内参照)とその関連定義(includeフォールダー参照)の発展を利用できる、というのが最大の恩恵です。これから少しCOMを勉強してみましょう。

 

最後にCommandProc.hを(解説:)付きで載せます。

 

【CommandProc.h】

//////////////////////////////////////////
// CommandProc.h
// Copyright (c) 11/15/2021 by BCCSkelton
//////////////////////////////////////////

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

    //起動時のカレントディレクトリーを記録
    GetCurrentDirectory(MAX_PATH, m_CPath);
//(解説:起動時のCommand.exeのパスをカレントディレクトリーのメンバー変数に記録しておきます。)

    //ボタンにツールヒントコントロールを付ける
    SetToolTip(m_hWnd, IDC_CD, "ディレクトリーを変更します");
    SetToolTip(m_hWnd, IDC_EXE, "引数付きで外部コマンド(実行ファイル)を実行します");
    SetToolTip(m_hWnd, IDC_DIR, "現在のディレクトリーでExplorerを起動します");
    SetToolTip(m_hWnd, IDC_CMD, "現在のディレクトリーでコマンドプロンプトを起動します");
    SetToolTip(m_hWnd, IDC_PS, "現在のディレクトリーでパワーシェルを起動します");
    SetToolTip(m_hWnd, IDOK, "終了します");
//(解説:前にやったようにコントロールに吹き出しヒントを付けます。)

    return TRUE;
}

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

    //終了時にログがあれば記録する
    if(*m_Log.ToChar()) {
        //本日のローカル日付をdate文字列に入れる
        SYSTEMTIME st;
        char date[12];
        GetLocalTime(&st);
        wsprintf(date, "%2d-%2d-%4d", st.wMonth, st.wDay, st.wYear);
//(解説:dateは"mm-dd-yy"書式の日付を記憶します。)

        //ファイル名("(ファイルパス)\LogOfMM/DD/YYYY.log")をnmにセット
        CSTR nm;
        CARG arg;
        nm = arg.Path();
        nm = nm + "\\LogOf";
        nm = nm + date;
        nm = nm + ".log";
//(解説:CSTR変数nmにファイルパス、名を入れます。)

        //ログを保存する(後にバッチファイルとして使える)
        m_Log.ToFile(nm.ToChar());

//(解説:CSTRのメンバー変数m_Logにあるデータを書き出します。)
        MessageBox(m_hWnd, "操作ログを保存しました\n(バッチファイルとして使用することができます)", "操作ログ", MB_OK | MB_ICONINFORMATION);
    }
    return TRUE;
}

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

    PostQuitMessage(0);
    return TRUE;
}

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

    //移動先パスを取得
    char* cp = cmndlg.GetPath(m_hWnd, "移動先ディレクトリーの選択");
    if(cp) {
        SetCurrentDirectory(cp);
        //移動先パスを(m_CPathに記録
        lstrcpy(m_CPath, cp);
        //操作ログを記録
        m_Log = m_Log + "cd \"";
        m_Log = m_Log + cp;
        m_Log = m_Log + "\"\n";
        return TRUE;
//(解説:フォールダー(ディレクトリー)選択ダイアログを使って移動先のパスを取り、カレントディレクトリーとして記録して、ログに書きます。)

    }
    else {
        MessageBox(m_hWnd, "操作がキャンセルされました", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
}

bool CMyWnd::OnDir() {

    //エクスプローラーを/eオプションで起動
    CSTR path = "explorer.exe /e, ";
    path = path + m_CPath;
    WinExec(path.ToChar(), SW_SHOWNORMAL);
    return TRUE;
//(解説:コメント通りです。)

}

bool CMyWnd::OnExe() {

    //IDD_ARGダイアログを開く
    exedlg.DoModal(m_hWnd, "IDD_EXE", exedlgProc, m_hInstance);
//(解説:IDD_EXEダイアログに処理を任せます。)

    //ダイアログの操作でカレントディレクトリーが変更された可能性がある為
    SetCurrentDirectory(m_CPath);

//(解説:IDD_EXEダイアログを開く前のカレントディレクトリーに移動します。)
    return TRUE;
}

bool CMyWnd::OnCmd() {

    //コマンドプロンプトを起動
    WinExec("cmd.exe", SW_SHOWNORMAL);
//(解説:DOS窓が開きます。)

    return TRUE;
}

bool CMyWnd::OnPs() {

    //コマンドプロンプトを起動
    WinExec("powershell.exe", SW_SHOWNORMAL);
//(解説:PowerShellを起動します。)

    return TRUE;
}

bool CMyWnd::OnIdok() {

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

bool CMyWnd::OnCancel() {

    SendMsg(WM_CLOSE, 0, 0);
    return TRUE;
}
//(解説:前にも書きましたが、コントロールが無くても'Esc'キー対応の為に書いておきます。)


///////////////////////////////
//IDD_EXEダイアログの関数定義
//ウィンドウメッセージ関数
///////////////////////////////
bool EXEDLG::OnInit(WPARAM wPram, LPARAM lParam) {

    //クライアントエリア初期化
    RECT rec;
    GetClientRect(m_hWnd, &rec);
    m_Width = rec.right - rec.left;        //405
    m_Height = rec.bottom - rec.top;    //371
//(解説:コメントの数値はCommand.rcのダイアログの初期値です。)

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

//(解説:ステータスバーの為に必要です。)
    //ステータスバー登録-SetHandle(hWnd))
    SBar.SetHandle(GetDlgItem(m_hWnd, IDC_STATUSBAR));
    //ステータスバー区画設定
    int sec[1] = {-1};
    SBar.SetSection(1, sec);
    //ステータスバー文字列設定
    SBar.SetText(0, "実行ファイル名-「ファイル」ボタンで選択");
//(解説:ステータスバーの初期化です。ステータスバーには実行ファイルを表示させます。)

    //ファイルボタンにツールヒントコントロールを付ける
    SetToolTip(m_hWnd, IDC_FILE1, "実行ファイルを選択します");
    SetToolTip(m_hWnd, IDC_FILE2, "引数1にファイルパス・名を使用します");
    SetToolTip(m_hWnd, IDC_FILE3, "引数2にファイルパス・名を使用します");
    SetToolTip(m_hWnd, IDC_FILE4, "引数3にファイルパス・名を使用します");
    SetToolTip(m_hWnd, IDC_DONE, "最終コマンドを標示します");
    SetToolTip(m_hWnd, IDOK, "最終コマンドを実行します");
    SetToolTip(m_hWnd, IDCANCEL, "終了します");
//(解説:ツールヒントの設定です。)

    //コマンドエディットコントロールの初期化
    g_CE.m_hWnd = GetDlgItem(m_hWnd, IDC_EXEDIT);
    g_CE.m_hInstance = m_hInstance;
    g_CE.SetFont(8, "MS 明朝");
//(解説:CCMDEDITのインスタンスg_CEのウィンドウとインスタンスのハンドルにIDC_EXEのものを代入し、フォントを設定します。)

    return TRUE;
}

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

    //ステータスバー再設定
    SBar.AutoSize();
    //コントロールの位置、サイズ変更
    RECT rec;    //矩形取得用
    POINT pt;    //スクリーン座標変換用
    int w, h;    //幅、高さ計算用
    int diffx = LOWORD(lParam) - m_Width;    //前回と今回の差分
    int diffy = HIWORD(lParam) - m_Height;    //前回と今回の差分
    m_Width = LOWORD(lParam);                //今回の幅
    m_Height = HIWORD(lParam);                //今回の高さ
//(解説:今回のサイズ変更の際のコントロール移動は差異を使って行います。考え方はWM_SIZEが呼ばれる度に前回の幅と高さとの差異を計算し、コントロールの位置にx, yの差異を反映させ、新しい幅と高さを記録する、という処理です。)

    //左右移動のみ
    //IDC_FILE1
    GetWindowRect(GetDlgItem(m_hWnd, IDC_FILE1), &rec);    //ウィンドウ位置取得
    w = rec.right - rec.left;
    h = rec.bottom - rec.top;
    pt.x = rec.left;
    pt.y = rec.top;
    ScreenToClient(m_hWnd, &pt);
    MoveWindow(GetDlgItem(m_hWnd, IDC_FILE1), pt.x + diffx, pt.y, w, h, TRUE);
    //IDC_DONE
    GetWindowRect(GetDlgItem(m_hWnd, IDC_DONE), &rec);    //ウィンドウ位置取得
    w = rec.right - rec.left;
    h = rec.bottom - rec.top;
    pt.x = rec.left;
    pt.y = rec.top;
    ScreenToClient(m_hWnd, &pt);
    MoveWindow(GetDlgItem(m_hWnd, IDC_DONE), pt.x + diffx, pt.y, w, h, TRUE);

//(解説:前回の位置を取得し、コントロールのサイズは変更しないのでw, hを変えずにx位置のみに差異を反映させます。)

    //幅変更のみ
    //IDC_EDIT1, IDC_EDIT2, IDC_EDIT3
    for(int i = IDC_EDIT1; i <= IDC_EDIT3; i++) {
        GetWindowRect(GetDlgItem(m_hWnd, i), &rec);    //ウィンドウ位置取得
        w = rec.right - rec.left;
        h = rec.bottom - rec.top;
        pt.x = rec.left;
        pt.y = rec.top;
        ScreenToClient(m_hWnd, &pt);
        MoveWindow(GetDlgItem(m_hWnd, i), pt.x, pt.y, w + diffx, h, TRUE);
    }

//(解説:前回の位置を取得し、コントロールの高さは変更しないのでwとx位置のみに差異を反映させます。)

   //上下移動のみ
    //IDCANCEL
    GetWindowRect(GetDlgItem(m_hWnd, IDCANCEL), &rec);    //ウィンドウ位置取得
    w = rec.right - rec.left;
    h = rec.bottom - rec.top;
    pt.x = rec.left;
    pt.y = rec.top;
    ScreenToClient(m_hWnd, &pt);
    MoveWindow(GetDlgItem(m_hWnd, IDCANCEL), pt.x, pt.y + diffy, w, h, TRUE);

//(解説:前回の位置を取得し、コントロールのサイズは変更しないのでw, hを変えずにy位置のみに差異を反映させます。)

    //左右上下移動
    //IDOK
    GetWindowRect(GetDlgItem(m_hWnd, IDOK), &rec);    //ウィンドウ位置取得
    w = rec.right - rec.left;
    h = rec.bottom - rec.top;
    pt.x = rec.left;
    pt.y = rec.top;
    ScreenToClient(m_hWnd, &pt);
    MoveWindow(GetDlgItem(m_hWnd, IDOK), pt.x + diffx, pt.y + diffy, w, h, TRUE);

//(解説:前回の位置を取得し、コントロールのサイズは変更しないのでw, hを変えずにx, y位置のみに差異を反映させます。)

    //幅高さ変更
    //IDC_EXEDIT
    GetWindowRect(GetDlgItem(m_hWnd, IDC_EXEDIT), &rec);    //ウィンドウ位置取得
    w = rec.right - rec.left;
    h = rec.bottom - rec.top;
    pt.x = rec.left;
    pt.y = rec.top;
    ScreenToClient(m_hWnd, &pt);
    MoveWindow(GetDlgItem(m_hWnd, IDC_EXEDIT), pt.x, pt.y, w + diffx, h + diffy, TRUE);
//(解説:前回の位置を取得し、左上の未固定してサイズ位置に差異を反映させます。)

    //クライアントエリアを再描画
    InvalidateRect(m_hWnd, NULL, TRUE);
    return TRUE;
}

bool EXEDLG::OnMinMax(WPARAM wParam, LPARAM lParam) {

    //典型的なウィンドウのサイズ制限処理
    MINMAXINFO *pmmi;
    pmmi = (MINMAXINFO*)lParam;
    pmmi->ptMinTrackSize.x = 421;    //クライアントエリア405
    pmmi->ptMinTrackSize.y = 410;    //クライアントエリア371

//(解説:前にもやりましたが、ウィンドウの最小サイズ設定の定番処理です。)

    return FALSE;                    //処理はDefWndProcに任す
}

///////////////////////////////
//IDD_ARGダイアログの関数定義
//コントロール関数
///////////////////////////////
bool EXEDLG::OnFile1() {

    //実行ファイルパス、名を取得
    char* cp = cmndlg.GetFileName(m_hWnd, "実行ファイル(*.exe)\0*.exe\0\0");
    if(!cp) {
        MessageBox(m_hWnd, "操作がキャンセルされました", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
    //ステータスバー標示
    SBar.SetText(0, cp);
    SBar.SendMsg(SB_SETTIPTEXT, 0, (LPARAM)cp);    //ToolTipをつける
    //実行ファイル名を""で囲んで保存
    m_FileName = "\"";
    m_FileName = m_FileName + cp;
    m_FileName = m_FileName + "\"";
    return TRUE;

//(解説:ファイル選択ダイアログで実行ファイルを選択しメンバー変数に記録します。また、ファイルパス、名は""で囲みます。)

}

bool EXEDLG::OnFile2() {

    char* cp = cmndlg.GetFileName(m_hWnd, "対象ファイル(*.*)\0*.*\0\0");
    if(cp) {
        //引数を""で囲む
        CSTR Arg = "\"";
        Arg = Arg + cp;
        Arg = Arg + "\"";
        SendItemMsg(IDC_EDIT1, WM_SETTEXT, 0, (LPARAM)Arg.ToChar());
        return TRUE;
    }
    else
        return FALSE;
}
bool EXEDLG::OnFile3() {

    char* cp = cmndlg.GetFileName(m_hWnd, "対象ファイル(*.*)\0*.*\0\0");
    if(cp) {
        //引数を""で囲む
        CSTR Arg = "\"";
        Arg = Arg + cp;
        Arg = Arg + "\"";
        SendItemMsg(IDC_EDIT2, WM_SETTEXT, 0, (LPARAM)Arg.ToChar());
        return TRUE;
    }
    else
        return FALSE;
}

bool EXEDLG::OnFile4() {

    char* cp = cmndlg.GetFileName(m_hWnd, "対象ファイル(*.*)\0*.*\0\0");
    if(cp) {
        //引数を""で囲む
        CSTR Arg = "\"";
        Arg = Arg + cp;
        Arg = Arg + "\"";
        SendItemMsg(IDC_EDIT3, WM_SETTEXT, 0, (LPARAM)Arg.ToChar());
        return TRUE;
    }
    else
        return FALSE;
}

//(解説:ファイルボタンを押すとファイル選択ダイアログが開き、引数としてのファイルパス、名をカッコつきでエディットボックスに表示します。引数がファイルではない場合は直接エディットボックスにオプション等を入力します。)

 

bool EXEDLG::OnDone() {

    char arg[MAX_PATH];
    CSTR Arg = m_FileName;    //最初に実行ファイル名が入る
    if(SendItemMsg(IDC_EDIT1, WM_GETTEXT, MAX_PATH,(LPARAM)arg)) {
        Arg = Arg + " ";
        Arg = Arg + arg;
    }
    if(SendItemMsg(IDC_EDIT2, WM_GETTEXT, MAX_PATH, (LPARAM)arg)) {
        Arg = Arg + " ";
        Arg = Arg + arg;
    }
    if(SendItemMsg(IDC_EDIT3, WM_GETTEXT, MAX_PATH, (LPARAM)arg)) {
        Arg = Arg + " ";
        Arg = Arg + arg;
    }
//(解説:最終コマンドをCSTR変数Argに入れます。まず実行ファイル名を入れ、引数があればスペースと引数を追加します。)

    //最終編集処理のためにIDC_EXEDITへ
    SendItemMsg(IDC_EXEDIT, WM_SETTEXT, 0, (LPARAM)Arg.ToChar());
//(解説:IDC_EDITに最終コマンドを表示させます。)

    return TRUE;
}

bool EXEDLG::OnIdok() {

    //最終編集後のIDC_EXEDITの文字列をbuffに取得
    char buff[MAX_PATH * 2];    //260 x 2 = 520
//(解説:もっと長くしてもらっても結構ですが、まずこの程度でよいでしょう。)

    if(SendItemMsg(IDC_EXEDIT, WM_GETTEXTLENGTH, 0, 0) > MAX_PATH * 2) {
        MessageBox(m_hWnd, "文字数が多すぎます", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
    SendItemMsg(IDC_EXEDIT, WM_GETTEXT, MAX_PATH * 2, (LPARAM)buff);
    //m_Cmdへ代入して引き渡す
    Command.m_Cmd = buff;
    //m_Logに記録
    Command.m_Log = Command.m_Log + Command.m_Cmd;
    Command.m_Log = Command.m_Log + "\n";
    //コマンドの実行
    g_CE.Exec(Command.m_Cmd.ToChar());

//(解説:m_Cmdというメンバー変数に代入、主ダイアログのログ変数に記録して実行します。ダイアログは開きっぱなしです。)

     return TRUE;
}

bool EXEDLG::OnCancel() {

    //ESCキー対応
    EndModal(TRUE);
    return TRUE;
}

//(解説:今回はIDOKでダイアログを閉めないようにしたので、IDCANCELのみがダイアログを終了させます。)

 

如何でしょう?処理内容はCCMDEDITクラスのみが初出ですが、BCCFOrmandBCCSkeltonの復習課題として簡単かつ分かりやすいと思います。これを使ってCUIのコマンドツールを色々といじくってください。うまくいけばログを編集してバッチファイルにできます。