引き続き、新PC導入で始めた64bitコンピューティング学習のお話です。(64bit CPU対応OSはWindows XPからあったんですね。縁がなかったので、全然知らなかった。汗;)

 

1.32bit CPUと64bit CPUの内部構造対比

Z80、8086でアッセンブラーをやっていたので、アドレス指定レジスターや演算レジスター(アキュームレーター)などの概念はあり、80386で始まるx86命令のCPUとx64(注)CPUの違いに関心がありました。

調べたところ、記事は古いですが、これが両社の対比で一番わかりやすいと思います。

Fig. 1に出ているのが16 (AX)、32 (EAX)、64 (RAX)bitのレジスターの対比で、それらのレジスターの集合具合(アーキテクチャー)がFig. 2でしょうか。演算レジスターが倍になっているところが印象的ですね。

注:以下の2のとおり、x64でくくれる互換性はあるものの、厳密には異なる命令セットが存在しています。

 

 

2.64 bit CPUの命令セット

では、大幅に強化されたCPUに対する命令はどうなっているのか、とみると、大手インテルは古くから64 bit CPUを研究、開発していたようですが(IA-64)、現在のPCの命令セットを商業的にいち早く確立したのはAMD(AMD64)だったようです。現在のWindows用CPUの命令セットは互換性があるということですが、単に「x64」といってもその中身は「x86-64, x86_64, x64, AMD64, IA-32e, EM64T, Intel 64」等ごちゃごちゃあるようです。(注)

注:wikiによればAMD64は「全ての32ビットの命令セットが実装されているため、32ビットのx86実行ファイルは、互換性あるいは性能の損失なしに動作させられる」のに対し、インテルのIA-64は「インテルのIA-64では32ビットコードの性能低下が問題となったが、これは命令セットが全く違うためにエミュレート実行していたため」というような差異が認められる。

 

3.Windowsにおけるx64チップでのx86ソフトウェアの実行

これが一番気になるところですが、ネイティブ64 bitのOSであるWindows 11をはじめとするウィンドウズでは、x64命令のCPUを持つハードウェアに対してネイティブ64 bitカーネルを持ち、x64ネイティブモードで動作するのはもちろん、WOW64(Windows 32 on Windows 64)というサブシステムのレイヤーをかませてx86命令の32 bitソフトウェアも(ユーザーが意識することなく、またロス最小で)実行することができるそうです。(注)

注:これもちょっと古い記事ですが、図がわかりやすいので参考としてください。またここでも「(ただし、IA-64上のWOW64では、x86のエミュレーターにより動作するため、同等のパフォーマンスとはならないので注意してほしい)」と書かれています。(↓のMSのサイトでもはっきりと「WOW64 は x86 エミュレーター」と書かれていますね。)

 

とはいえ、ご本家の話を聞かなければいけませんね。

64 ビット バージョンの Windows での 32 ビット プログラムの互換性に関する考慮事項の概要

32 ビット アプリケーションの実行

 

いずれにしても、

(1)「64 ビット Windowsでは、16 ビット Windows ベースのアプリケーションの実行はサポートされていない」

(2)「32 ビット ドライバーはサポートされ」ない

ことに注意してください。(なので、私が大事にしている16 bit作曲ソフトはWin 10マシンでのみ動かすことになります。また、素人プログラマーのソフトでドライバーまで組んでいるというのはあまりないので、これはほとんど関係ないかと思いますね。)

 

以上でx64の概要とx86との互換性がどうなっているのか、「大体分かる」ことができたのではないでしょうか?

 

前回のファーストインプレッションは主にハード面についてだったので、今回はソフト面、というよりもストレートに「Windows 11」についてのファーストインプレッションについて述べてみましょう。

 

まず、「Windows 11とは何か?」について予備知識がない方は↓をご覧になられるとよいでしょう。

 

Microsoft Windows 11 (wiki)

 

私のファーストインプレッションを要すれば、

 

(1)32bit時代の終焉を告げる64bit OSシリーズ

(2)システム要件があり、2021年当初は混乱があった(当時のPCの60%が以降不可能という話が書かれています。)

(3)目立つ機能的な特徴は「ウィジット(widget-装置、細工、仕掛け)」のみで、インターフェースは大きな差異はない(私的には、インターフェースのビミョーな差異が逆に混乱を誘うと感じたのですが?)

(4)CPUが相当早くなっていることは個別のタスク処理でわかるのですが、ウィンドウズは起動とEdgeの画面遷移がやたら遅く感じられます。(Ryzenだけか?↑の記事参照)

 

という印象です。(特に速度にかかわる米PC雑誌の評価はうなずかざるを得ないですね。)

 

そんなこんなで、自作Win 10マシンは当面廃棄することができないでしょう。(注)

 

注:今も新PCとMicrosoft Edgeでブログを書いていますが、書きながら調べ物をしたり、他のサイトに行く際の遷移時間が気になります。なお、起動に関してはWin 10マシンと比べると立ち上がりまではのろのろしていますが、その後の処理はサクサクとしています。

 

次はもう少しx86tox64の互換性(というか、x64でどのようにx86を動かしているか)について知らないので、ちょっと調べてみます。

 

先日の

【無駄話】最近のトピック

で書きましたが、20年ぶりにBCCを使ってECCSkeltonまで開発した第4世代Core i-7のWin10機の後継機を衝動的に購入しましたので、ブログネタで書いてゆきます。

 

1.どんなPC?

今のジャンルでいうと「ゲーミングミニPC」ということになるようです。

初めてのAMD機で、やはり高性能のほうが良いので私の目的には大分オーバースペックですが、Ryzen 7 5800H(8コア、16スレッド、Radeon Vega 8 GPU内蔵)のこれにしてしまいました。

OSはあれだけ無用としていたWin11(x64)。なんだかんだ言いながら、結構Up-to-dateなマシンです。(他のミニPCと比して気に入ったのは、その頑丈そうな筐体と比較的充実したインターフェースでした。)

 

2.いつ、どのように買ったの?

前のブログでも書きましたが、現在のWin10マシンで十二分なのですが、最近キュリキュリとイオンを発するのと時々ブラックアウトするので、いざという時のための「冗長化(リスクマネジメント用語ですね)」で新しいPC事情を勉強し、自分の必要と趣味を具体化させて絞り込んだ中の一品でしたが、アマゾン得意のタイムセールで背中を押されました。

問題はいざ買うとなると、

(1)新しいPCを買うと現在のWin10マシンをどうするか?

(2)ディスプレー(現在の19インチディスプレー13年落ちのDellでDVIインターフェース)を使い続けるのか?

(3)キーボード、マウス等は?

(4)共通環境のためのソフトウェアやデータ移管は?(注)

(5)二つのPCを接続するの?

(6)大体廃棄するにも無料サービスがあるの、PCの場合、ディスプレーの場合それぞれでどう?

という問題が発生します。

注:ちなみに現在の自作PCに移行する前のVistaマシンは、データ移管の際に事故を起こして多くのファイルとデータを失いました。そして移管デバイスを買っていたのですが、結局は使わずにクロスケーブル接続で移管しました。

 

いろいろと検討しましたが、とりあえずは正常な移管のために廃棄等は先送りとして、まずは完全に二台体制にすることから始めよう、というのが結論でして、翌日に23インチディスプレー(display port使用)、翌々日に無線キーボード+マウスセットを毎日発注し、台風の中デリバリーされることになりました。

 

3.今はどうなっているの?

(1)概況

すべての物品が届き、配線し、狭いデスクから不要なものを廃棄して2台のディスプレーを並べ、マウス、キーボードを交互に使うようになりました。

Win 10マシンの重要なデータは1TBのHDにバックアップを取っているのでこれを移管し、不要なものを削除し、使用環境の互換性を持たせ、一応新PCで作業ができるようになりました。(このブログも新PCで書いてます。)

 

(2)トラブル

これといった大きなトラブルはありませんが、Windowsのセットアップで10と11のビミョーな相違とか、「MicrosoftアカウントのID、パスワード、Windows HelloのPINとサインインオプション、WindowsのPIN、ローカルアカウントのID、パスワード、(PC接続の際の)イーサネットワークの識別子とパスワード」が乱舞して混乱しなかったと言ったらウソですね。(特にPC接続はネットワークとインターネット、共有、システム、セキュリティとアカウント等どこに何の設定が出ているのかが結構ムズいです。これもWin 10とWin 11でビミョーに違うんですよね。)いずれにせよ、新PCにDVDがないので前のWin 10マシンのDVDを使うことができるまでやや奮闘しましたね。

あと、RyzenとRadeonチップセットの付属ドライバーがすでに古くなっていたようで、AMDのサイトに行って必要なドライバーを自動診断でダウンロードし、インストールしました。(和文の案内や説明のあるサイトがなかったので、英語ができないとちょっとつらい思いをするかも、です。)その結果、AMDのハウスソフトが使えるようになりました。

 

(3)ソフトウェアの互換性

これが一番気になりましたが、結果的に言うと完全にダメになったのが「1990年代のWin3.1の16ビットソフト」で、DVDをWin 10マシンから借用してインストールした32ビットゲームソフトは一応動くのですが、時々落ちます。(まぁ、ソフトのほうのお行儀が悪いゲームのようですが。)朗報は、BCCSkeltonやECCSkeltonのWin32(x86)プログラムは皆元気に動いてくれたことですね。

 

まぁ、ベンチマーク性能は数十倍よくなっていますが、普通に使う分には速度差は「体感」できるほどではないですね。起動なんてWin 10のほうがよっぽど早いです。まぁ、64ビットで動かしているのだから体力を使うのでしょうが。

 

ここまでの道のりで、やはりWin 10マシンは日常事務や開発機として(1TBのSSDもあるし)、今後も使い続ける予定です。逆に新PCはエンターテインメント(ウェブ、音楽、動画その他処理の重そうなもの)に絞って使うようにするかな、と漠然と考えています。

 

今後も折に触れ、トピックが出てくれば【新PC】シリーズとしてブログを書いてゆこうと思いますので、よろしくお願いします。

 

22年前からbcc55、2年前からbcc102を使っており、自作ツールと共に素人プログラマーが学習や趣味でプログラミングをするには十分な能力と実用性があると思いますが、一つ愚痴を言わせていただければデバッグ環境は余り大したことが無い、ということです。

 

1.素人プログラマーに好ましい、優しいデバッグ環境

これは基本的にソースコードレベルで、エラーや不具合の現象のみならず原因を(WYSWYGというか視覚的に直観できるよう)指摘してくれるデバッガーでしょう。例えば昔MicrosoftのVisual Basicを使ったことがありますが、あれがまさにそれですね。例えば0で割ったり(以下↓例参照)、誤って保護領域をコールしてしまい、例外状況になった場合、問題となるソースの行を赤反転させて実行を停止してくれます。

 例:   C = A / B    Error: Division by zero

デバッギングの玄人さんであれば、ブレークポイント迄とかステップ毎とかで実行させ、都度レジスター内容や変数(スタック)の内容を確認してトレースできるのでしょうが、それも関数アドレスや変数などのシンボルが無いと可也り「バイナリー側の人間(注)」でないと無理です。

注:8 bitのZ80でコンピュータープログラミングを始めた頃はアッセンブラーもなく、巷の好き者はZ80のニーモニック(LD、ADD、SUBとかJPとかの 機械語)はおろか、直接バイナリーコード(例:JP 5000H→C3 00 50:アドレス16進法の5000Hにジャンプせよ、の意味でアドレスがひっくり返っているのはリトルエンディアンだから。)でプログラムを組める「人間アッセンブラー」もいましたね。

 

2.Turbo Debugger

2000年からフリーで公開されたbcc55セットは、更におまけでTorbo Debuggerが付いてきましたが、基本的にTurbo DebuggerはMS-DOSのコンソールベースのアプリであり、且つウィンドウズのようなイベント割り込み処理ベースのシステムではソースを示すことができない為、結局はソースベースにではデバッギングできませんでした。

1例として先般紹介したBCC2ECCをTurboDebuggerにかけたイメージを紹介しますが、短いBCC2ECC.cppだけ逐次実行を表す「〇」付の行を示すだけで他のソースは表示しません。

 

3.Visual StudioのWinDbg

一方ご本家はVisual Studioではもちろん、またWindows SDK Kitsをダウンロードすれば利用できるウィンドウベースのデバッガーを提供しています。(OSが不正終了した場合に解析情報をMicrosoftへ送る使い魔でもあるようです。)以下に同じくBCC2ECCのソースとexeファイルを読み込ませた状態のイメージを示しますが、やはり20年の時間の差を感じさせるモダンでスマートなものです。

しかし、矢張り残念なことにbccで開発したソフトは(コンパイルの際に-vスイッチを付けてシンボルを追加できるようですが、勿論互換性はなく)バイナリーデータ以外は情報が無いためにソースベースでのディバッギングは不可能です。Microsoftはデバッグ用にpdb(program database)ファイルというものをコンパイラーに排出させ、これを読んだWinDbgが多様なシンボルを使ってソースレベルでバッギングができるようです。まぁ、残念ですがBCCSkeltonやECCSkeltonの開発ではこのような環境は使えませんね。

 

4.結語

先般の不具合騒動の際に、何か利用できるデバッガーはないか、提供元のEmbarcaderoは勿論、色々と探した挙句矢張りソースレベルではプログラムの中に「罠」を仕掛けてデバッグするしかない(注)、ことを思い知らされました。まぁ、そういう環境でプログラミングをすることにより、幅広い雑知識をため込んで鋭敏なアンテナを張り巡らし、洞察力を養うトレーニングになるのですが...

注:実行時に不法なメモリーの浸蝕をするような不具合(Aの場所で犯したコードエラーがBの場所で不具合を発生させるようなエラー)には役に立たないことが分かりましたが...

 

まぁ、それで飯を食っているわけでもなく、単に趣味(というかボケ防止)の為の暇プロには、(ストレスは大きいですが)デバッギングすらも楽しみとしなければならないのですが。

 

開発ネタが無いので、最近のトピックなどを書いてみます。

 

1.プライベートソフトのアップデート

ECCSkeltonが完成し、BCC2ECCができたので、何か本格的にECCSkeltonでつくろうかな、と考えていたのですが、それより前にやらなくてはならないプライベートのソフトウェアのアップデートをやっていました。

アップデートの理由は二つ。

一つはCSDIウィンドウにエディットボックスを付けてテキスト情報を表示するだけ、という索漠とした見栄えをフツーにダイアログ化したことです。

もう一つは前作が自作の暗号化を使っていたのですが、今回はウィンドウズの標準暗号化(IDListと同じ奴です)を使ったことです。

作る序に、文字列データからカンマ区切り文字列を切り出したり、行を切り出したりする関数をアップデートしました。前者はIDListやFileListでも使っていたのでそれらの関数もアップデートしました。

本日一応完成し、前作と同じ動作を正常に動作することを確認しました。

しかし、だんだんと頭が衰えてきて、凡ミスが増えてきたような気がします。(いつまでプログラミングができるのだろうか?)

 

2.ハードウエアの更新

こっちの方が衝撃度が高いでしょうか?

 

現在使っているPCセットは、PC-次兄から譲り受けた自作機でCore-i7  3.50GHz、メモリー16GBと1TBのSSDが付いているフルタワーなんですが、Windows 10は32bit(x86)でCPUも第4世代のものなので十分に速いですが、スペック的に言うともうout-of-dateの感は否めません。また、x64のウィンドウズ(Windows 11)での動作確認ができないこともネックとなってきています。また、次兄は現在Ryzen 9のマシンを使っているということですが、長兄が発作的にアマゾンで買ったミニPC(第8世代Core-i5マシン)が廉価なのに結構早いので、ベンチマークを調べてみたら第4世代のi7なんかよりも速いことが分かりました。

 

そんなこんなで、ここ2ヶ月ほどミニPCに絞って色々と漁っていたのですが、最近の円安で結構値段が上がってきており、当分は見(ケン)かな、と思っていましたところ、性能的にはここら辺が自分には合っているのかなと考えていた候補の一つ、Ryzen 7 5800HのミニゲーミングPCがアマゾンタイムセールで2万円弱ディスカウントされているのを発見。台風が来るというのに、ポチっと押してしまいました。そうしたら、まだ丸一日も経っていない台風の中、中国のミニPCがデリバリーされました。一方、早速インターフェースを調べて発注した国産の電源コードとディスプレーコードが丸一日以上かかってもまだ来ないという、最近の「日本の現状」を感じる(注)毎日です。(従って新しいWindows 11機はまだ火を入れていません。)

注:1991年に初めて米国に赴任したのですが、電話で「今日持ってゆくよ」と運送屋が言うので、「午前になるのでしょうか?午後になるのでしょうか?」と聞くと、「なんともいえねーな」とのこと。「アメリカだよなぁ。」といっていたのですが...

 

いずれにせよ、これから

(1)古くから遊んでいる「1999年のSierra OnLineのゴルフゲームソフトとシステムソフトの麻雀ゲームソフト」をはじめとした(x86)32bitソフトがWindows 11(x64)でどの程度動くのかをじっくり確認しながら、旧いフルタワーをどうするか決めること

(2)現在使っているhpの19インチDVIモニター(HMDIやdisplay portインターフェースなし)をどうするか決めること

(3)新しくモニターを購入したら↑の(2)を、↑の(1)とともに、または別に(注)、処分するかどうか決めること

をじっくり考えていこうと思っています。また、x86→x64への移行や、そのパフォーマンスの違いなどをブログでも紹介していこうかと思っています。

注:PCの処分だと無料のリネットジャパンがお勧めですが、モニター単体だと無料処分が難しく、\1,100~数千円かかることになります。

 

追補:夕方になってやっとコード類が届きました。一方、午後に発作的にディスプレーをポチっとやったのですが、もう発送したとのこと。(インターフェースはdisplay portにしようと思っています。)x86の32bitマシンも捨てがたく、当面2台体制でやっていこうかな、と...

前に、

【旧bcc32の謎】誰か分かる人はいますか?-解けました m_(_ _)_m

で、次のように書きました。

 

ps. 実はCSTRクラスに関してはまだ怪談があり、それはある非公開のソフトで「外部変数で宣言したCSTRインスタンスにconst char*文字列を代入すると落ちる」という現象です。それも(そしてそれは真逆で)「bcc32(bcc55)でコンパイルすると正常に動作するのですが、bcc32c(bcc102)でコンパイルすると落ちる」のです。これも原因は不明であり、現在bcc55版だけを使っています。bcc2eccについてもbcc102版だけリリースすることになりそうです。

 

これも不思議で、MessageBoxで罠をかけて落ちる直前の箇所を調べていって↑のコメントになったのですが、他の箇所でCSTRの初期化や代入を行っても正常動作するので原因がつかめずにいました。その後昔のTurbo Debugger32やWinDbg(x86)デバッガー(注)を(ソースベースでは使えないのでバイナリーベースで)使って調べるも訳が分からず、色々なコードを書いたり消したりしていたら、ツールバーの「?」ボタンに文字が出るようになり、最後は正常にコンパイルできるもbcc32(bcc55)、bcc32c(bcc102)共にコンパイルは正常ですが、起動できない状態に陥りました。

注:このデバッガー経験については別途書きます。

 

結局この非公開ソフトを全く別の形式で書き直そうとして、ひょんなことからその原因が特定できました。それは、

 

【(非公開ソフト)Proc.h抜粋】

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

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

 

    //ツールバー登録-Init(hWnd, hIinstance, ID, Style)
    TBar.Init(m_hWnd, m_hInstance, TOOLBAR, WS_CHILD | WS_VISIBLE | TBSTYLE_TOOLTIPS);
    //ツールバーボタン用カスタムビットマップ追加
    TBar.AddBmp(m_hInstance, MAKEINTRESOURCE(IDI_TOOLBAR), 5);
    //ツールバーボタン追加
    TBBUTTON tbb[5];    //ん? ... ん? ... ん? ... ん?
    ZeroMemory(tbb, sizeof(tbb));
    tbb[0].iBitmap = TBar.m_id;
    tbb[0].fsState = TBSTATE_ENABLED;
    tbb[0].fsStyle = TBSTYLE_BUTTON;
    tbb[0].idCommand = IDM_EXIT;

    tbb[1].fsStyle = TBSTYLE_SEP;    //セパレーター

    tbb[4].fsStyle = TBSTYLE_SEP;    //セパレーター

    tbb[6].iBitmap = TBar.m_id + 4;
    tbb[6].fsState = TBSTATE_ENABLED;
    tbb[6].fsStyle = TBSTYLE_BUTTON;
    tbb[6].idCommand = IDM_VERSION;
  TBar.AddButtons(7, tbb);

TBBUTTON配列5個分に対して7個分を登録しています。恐らく最初はSkeltonWizardで5つのボタンを並べたのでしょうが、その後セパレーターを入れたくなり、マニュアルでセパレーターを二つ入れたので最後のAddButtonsは5→7に修正したのでしょうが、配列宣言の修正は見落としたことが原因でした。TBBUTTON配列宣言を「7」に修正した後はbcc32(bcc55)、bcc32c(bcc102)共に「コンパイルは正常(このようなエラーは検出してくれないのです)」、ツールバーボタンに訳の分からない文字列が現れることもなくなり、動作も正常となりました。

 

今回のデバグが難航したのは、

 

(1)不正コードが「CSTR変数への代入のコード」の箇所に(プログラム実行時に)TBBUTTON二個分侵食して、症状を出したので「現象の発生場所 != 原因」であった為、および

(2)bcc32Cではエラー(プログラム落ち)が発生したのに、bcc32では(当初の段階では)無難なところに配列変数を配置したので正常に動作してしまった為、

 

かと考えられます。

 

いずれにせよ、原因不明のバグで気落ちして、テンションが下がっていたので、やっとこれで気分が回復しました。

 

実はBCC2ECCをECCSkeltonに移植し、また↓の不具合

が発生しました。如何にも文字列処理の暴走っぽいのですが、訳が分からないまま、今朝早朝、

「あ"っ」

「またやっちまった」

ということで、デバッグ直観が降りてきました。

 

実は書く(告白する)のも恥ずかしくて死にそうなんですが、「元カノ(カレ)を捨てたのに、実はずーーーーーっと使っていた」のがバグの原因でした。(以下↑の馬鹿ブログからの引用)

元々の文字列(文字配列)m_strと、m_strの中でToFind文字列を発見したFound_atに注意して以下を見てください。

 

2.該当箇所のコード(デバッグ用のメッセージボックス付)

//文字列の置換
char* CSTR::Replace(char* ToFind, char* ToReplace, char* sp = 0) {

    
//CSTRのデータ長、検索文字長、置換文字長を計算する
    int OrgLen = lstrlen(m_str);
    int ToFindLen = lstrlen(ToFind);
    int ToReplaceLen = lstrlen(ToReplace);
  
 //spの指定が無ければCSTRデータの先頭から検索する
    if(!sp)
        sp =
m_str;
  
 //検索文字列ポインターの取得
    char* Found_at = strstr(sp, ToFind);        //検索文字列が無ければNULLを返す
    //検索文字があれば
    if(Found_at) {
(A)

        //置換後の文字列データバッファー(NULL終端付)の確保
        char* buff = new char[OrgLen - ToFindLen + ToReplaceLen + 1];
        *
Found_at = NULL;                        //NULL終端
        lstrcpy(buff, m_str);                    //検索文字列(ToFind)までをbuffにコピー
        lstrcat(buff, ToReplace);                //検索文字列(ToFind)の代わりに置換文字列をコピー
        lstrcat(buff, Found_at + ToFindLen);    //検索文字列(ToFind)以降の文字列をコピー
        buff[OrgLen - ToFindLen + ToReplaceLen] = NULL;    /付/NULL終端をける
        delete [] m_str;                        //CSTRデータ(m_str)を削除
        m_str = new char[OrgLen - ToFindLen + ToReplaceLen + 1];    //新しいデータ領域を確保
        lstrcpy(m_str, buff);                    //置換後のデータをコピー
(B)

        delete [] buff;                            //文字列データバッファーを破棄
    }
    return
Found_at;    //置換できなかった場合NULL、できた場合置換文字列の先頭ポインター
}

 

そうです。もともとの文字列m_strは一旦buffにコピーされ、捨てられ(delete)て、新しいm_strになります。しかし、元カノのFound_atをそのまま使っていた、というお粗末です。(一旦廃棄されたm_strのメモリー領域は再度m_strを作る際にかぶって使われると、結構問題なく動いちゃうこともあるので、本当のバグ原因が分からなくなり、bcc32を悪者にしてしまったようです。年を取ると「思い込むと自分が正しいと盲信し、その他のことを忘れてしまう」様子がよく分かると思います。)

 

ソリューションとしては、

(A)    DWORD dspl = Found_at - m_str;            //Found_atのm_str内の相対位置

(B)    Found_at = m_str + dspl;                //Found_atのm_str内の相対位置

というコードを挿入することで修正しています。

 

とんだお粗末、というか(自分でも愕然とする)ボケをかまして、いよいよリタイアかと、しょんぼりしてしまいした。

BCCSkelton、ECCSkelton、BCC2ECC全て本日修正アップロード済です。

 

追記:なお、前のブログで「何故こうなるのだろう?」という現象の説明をすると、次のようになります。

(1)ToFind文字列が見つからなければ文字列やメモリー操作はされないので問題はありません。

(2)最初のToFind文字列がm_strで見つかった場合、その先頭アドレスをNULLにしてbuffにコピーし、ToReplace文字列を追加して、最後に残ったm_str文字列をbuff追加するまでは問題ありません。が、buffをコピーするのは新しいm_strになります。にもかかわらず、Found_atはdeleteで破棄された旧m_strのメモリーの中です。

(3)次の検索は「破棄された旧m_strのメモリーの中」で行われ、ToFind文字列の先頭アドレスをNULLにするのは良いのですが(実際旧m_strのメモリーではそうしている)、buffにコピーする「m_str」は新しいm_strなので、第1回目で作成された新しいm_str文字列がすべてコピーされてしまうことになります。そして想定外に長い文字列がbuffにコピーされた後、お尻にToReplaceを追加することになるので、確保したメモリー領域を超えて書き込むことになって、落ちてしまいます。

 

BCC2ECCで遊んでいて、↑のような現象に遭遇しました。

 

またまた「え"っ」という反応をしたのですが、これはCSTRのToSJISFile関数のWideCharToMultiByte関数でエラーが出ているんだろうと考え、それを更に詳しく知るために、

 

【CSTR抜粋】

//SJISファイルへの書き込み
bool CSTR::ToSJISFile(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) {    //オープンできたか
        //WCHARをSJISへ変換した際の文字列長(バイト-NULL終端を含む)を取得
        DWORD dwBuffSize = WideCharToMultiByte(CP_ACP, WC_ERR_INVALID_CHARS, m_str, -1, NULL, NULL, NULL, NULL);
        if(!dwBuffSize) {
            DWORD dwError = GetLastError();
            switch(dwError) {
            case ERROR_INSUFFICIENT_BUFFER:
                MessageBoxW(0, L"Cause: ERROR_INSUFFICIENT_BUFFER", L"Error", MB_OK | MB_ICONERROR); break;
            case ERROR_INVALID_FLAGS:
                MessageBoxW(0, L"Cause: ERROR_INVALID_FLAGS", L"Error", MB_OK | MB_ICONERROR); break;
            case ERROR_INVALID_PARAMETER:
                MessageBoxW(0, L"Cause: ERROR_INVALID_PARAMETER", L"Error", MB_OK | MB_ICONERROR); break;
            default:
                MessageBoxW(0, L"Cause: unknown", L"Error", MB_OK | MB_ICONERROR); break;
            }

            CloseHandle(hFile);            //ファイルのクローズ
            return FALSE;
        }

上の赤字コードを入れてみたのですが、エラーで表示されたのは"Cause: ERROR_INVALID_FLAGS"でした。(厳密に変換できない場合エラーが多出するようです。)

 

それでは、ということでdwFlagsを厳格なWC_ERR_INVALID_CHARから最もストライクゾーンの広い

 

WC_NO_BEST_FIT_CHARS | WC_COMPOSITECHECK | WC_DEFAULTCHAR(注)

注:"The application should specify WC_NO_BEST_FIT_CHARS and WC_COMPOSITECHECK with the specific value WC_DEFAULTCHAR to retrieve all possible conversion results."(出典:Microsoft Docs

 

に変更してみたところ、変換エラーは発生せず、きちんとShift-JISテキストになりました。

 

結構難しいですね、文字コードって。

 

BCC2ECCツールん最後は、メンバー関数の定義をまとめたBCC2ECCProc.hファイルです。

 

【BCC2ECCProc.h】

//////////////////////////////////////////
// BCC2ECCProc.h
// Copyright (c) 09/06/2022 by BCCSkelton
//////////////////////////////////////////

/////////////////////////////////
//主ウィンドウCMyWndの関数の定義
//ウィンドウメッセージ関数
/////////////////////////////////

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

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

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

    PostQuitMessage(0);
    return TRUE;
}

//(解説:以上はSkeltonWizard通りコードです。)


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

    char* fn = cmndlg.GetFileName(m_hWnd, "BDPファイル(*.bdp)\0*.bdp\0\0",
                        TRUE, "bdp", ".", "プロジェクトファイルを開く");
    if(!fn) {
        MessageBox(m_hWnd, "ファイルが選択されませんでした。", "エラー",
                    MB_OK | MB_ICONERROR);
        return FALSE;
    }
//(解説:ファイルを開くダイアログでbdpファイルを指定させます。)

    else {
        //フルパスプロジェクトファイル名を記録
        lstrcpy(m_PathName, fn);
        m_Ext = strstr(m_PathName, ".bdp");    //拡張子の位置を記録

//(解説:bdpファイルのフルパス名をg_PathNameに保存。拡張子の'.'の位置を記録します。)

        //フルパスプロジェクトファイル名
        char* cp = m_PathName;
        while(cp < m_Ext) {
            if(*cp == '\\')
                fn = cp;                    //(最後の)'\\'の位置を記録
            cp++;
        }
        ++fn;                                //'\\'の次に進める

//(解説:最後の'\\'(\記号)fnに記録させ、それを一つ進めてパスを取り除きます。)

        for(cp = fn; cp < m_Ext; cp++)
            m_PrjtName[cp - fn] = *cp;        //プロジェクト名の保存

        SendItemMsg(IDC_EDIT, WM_SETTEXT, 0, (LPARAM)m_PrjtName);

//(解説:m_PrjtNameにはファイル名の内、「'.'拡張子」を外した文字列を保存し、IDC_EDITに表示ます。)

        MessageBox(m_hWnd, "選択されたBCCSkeltonプロジェクトのrcファイル、"\
                    "cppフィル、hファイルおよびProc.hファイルを順に"\
                    "ECCSkelton用に変換します。\r\n但し、メインウィンドウで"\
                    "ないダイアログがあれば、コマンドテーブル(CMDTABLE)の"\
                    "クラス名とテーブルをマニュアルで修正する必要があります。",
                    "説明", MB_OK | MB_ICONINFORMATION);

//(解説:これからの処理と、ユーザーのマニュアル修正について説明しています。)

        return TRUE;
    }
}

bool CMyWnd::OnIdok() {

    if(!*m_PathName) {
        MessageBox(m_hWnd, "未だファイルが選択されていません。", "エラー"    ,
                    MB_OK | MB_ICONERROR);
        return FALSE;

//(解説:処理対象が指定されていない場合はエラーとなります。)

    }
    //チェックボックスをチェックする
    if(SendItemMsg(IDC_OPTION, BM_GETCHECK, 0, (LPARAM)0) == BST_CHECKED)
        m_Option = TRUE;

//(解説:オプションがチェックされていればmOptionフラグを真にします。)

    //RCファイルの変更
    lstrcpy(strstr(m_PathName, ".bdp"), ".rc");
    ModifyRC();
    //CPPファイルの変更
    lstrcpy(strstr(m_PathName, ".rc"), ".cpp");
    ModifyCPP();
    //CPPファイルの変更
    lstrcpy(strstr(m_PathName, ".cpp"), ".h");
    ModifyH();
    //CPPファイルの変更
    lstrcpy(strstr(m_PathName, ".h"), "Proc.h");
    ModifyProcH();

//(解説:rc、cpp、h、Proc.hの各ファイル毎の処理を行います。)

   //再変換、再初期化、または終了する
    int res = MessageBox(m_hWnd, "変換処理されたECCSkelton用ファイルは"\
                        "\"_ECC\"が付いたファイルになっています。\r\n内容を"\
                        "確認して再度変換する場合は「はい」を、再初期化する"\
                        "場合は「いいえ」を、終了する場合は「キャンセル」を"\
                        "押してください。",
                        "続行確認", MB_YESNOCANCEL | MB_ICONQUESTION);
    if(res == IDNO)
        Init();
    else if(res == IDCANCEL)
        OnIdno();    //終了します。
    else
        return TRUE;
}

bool CMyWnd::OnIdno() {

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

////////////////////
//ユーザー関数定義
///////////////////
void CMyWnd::Init() {

    *m_PathName = NULL;
    m_Ext = NULL;
    *m_PrjtName = NULL;
    m_Option = FALSE;
    //エディットボックスを空にする
    SendItemMsg(IDC_EDIT, WM_SETTEXT, 0, (LPARAM)"");
    //リストボックスを空にする
    while(SendItemMsg(IDC_LISTBOX, LB_DELETESTRING, 0, 0));
    //チェックボックスを未チェックにする
    SendItemMsg(IDC_OPTION, BM_SETCHECK, BST_UNCHECKED, (LPARAM)0);
}

//(解説:メンバー変数とコントロールの初期化用の関数です。)

 

//(解説:以下各ファイルの変換ですが、何をどうしているかについては青字を読んでください。)

bool CMyWnd::ModifyRC() {

    CSTR File, fn(m_PrjtName);
    File.FromFile(m_PathName); 
    fn = fn + ".rc を処理します。";
    SendItemMsg(IDC_LISTBOX, LB_ADDSTRING, 0, (LPARAM)fn.ToChar());
    //共通
    File.ReplaceAll(", \"", ", L\"");    //[, "]→[, L"]
    //ダイアログ
    File.ReplaceAll("CAPTION \"", "CAPTION L\"");    //[CAPTION "]→[CAPTION L"]
    File.ReplaceAll("CONTROL \"", "CONTROL L\"");    //[CONTROL "]→[CONTROL L"]
    //メニュー
    File.ReplaceAll("POPUP \"", "POPUP L\"");        //[POPUP "]→[POPUP L"]
    File.ReplaceAll("MENUITEM \"", "MENUITEM L\"");    //[MENUITEM "]→[POPUP L"]

//(解説:以下は各ファイル共通の"\ECC.(拡張子)"保存処理です。)

    *m_Ext = NULL;
    fn = m_PathName;
    *m_Ext = '.';
    fn = fn + "_ECC.rc";
    File.ToFile(fn.ToChar());
    return TRUE;
}

bool CMyWnd::ModifyCPP() {

    CSTR File, fn(m_PrjtName);
    File.FromFile(m_PathName); 
    fn = fn + ".cpp を処理します。";
    SendItemMsg(IDC_LISTBOX, LB_ADDSTRING, 0, (LPARAM)fn.ToChar());
    File.ReplaceAll("BCCSkelton", "ECCSkelton");
    File.ReplaceAll("WinMain(", "wWinMain(");    //[WinMain(]→[wWinMain(]
    File.ReplaceAll("LPSTR", "LPWSTR");            //[LPSTR]→[LPWSTR]
    File.ReplaceAll(", SDIPROC", "");            //[, SDIPROC]→[]
    File.ReplaceAll(", ModelessProc", "");        //[, ModelessProc]→[]
    File.ReplaceAll("(\"", "(L\"");                //[("]→[(L"]
    File.ReplaceAll(" \"", " L\"");                //[ "]→[ L"]
    *m_Ext = NULL;
    fn = m_PathName;
    *m_Ext = '.';
    fn = fn + "_ECC.cpp";
    File.ToFile(fn.ToChar());
    return TRUE;
}

bool CMyWnd::ModifyH() {

    CSTR File, fn(m_PrjtName), work("ON_COMMAND(");
    File.FromFile(m_PathName); 
    fn = fn + ".h を処理します。";
    SendItemMsg(IDC_LISTBOX, LB_ADDSTRING, 0, (LPARAM)fn.ToChar());
    //共通
    File.ReplaceAll("BCCSkelton", "ECCSkelton");
    //メッセージ、コマンドテーブル共通
    work = work + m_PrjtName;
    work = work + ", ";
    File.ReplaceAll(work.ToChar(), "ON(");            //[ON_COMMAND(]→[ON(]
    File.ReplaceAll("ON_(", "//ON_(");                //[ON_(Win message)]→[//ON_(Win message)]
    File.ReplaceAll("ON_", "//ON_");                //[ON_]→[//ON_]
    File.ReplaceAll("(\"", "(L\"");                    //[("]→[(L"]
    File.ReplaceAll(" \"", " L\"");                    //[ "]→[ L"]
    File.ReplaceAll("char", "WCHAR");                //[char]→[WCHAR]
    //メインウィンドウのメンバー関数にCMDTABLEを追加
    File.ReplaceAll("//ウィンドウメッセージ関連\r\n\tbool", "//ウィンドウメッセージ関連\r\n\tCMDTABLE\t\t//OnCommand()関数宣言\r\n\tbool");
    //CSDIウィンドウの場合
    File.ReplaceAll("BEGIN_SDIMSG(", "BEGIN_CMDTABLE(CMyWnd)\t//クラス名がCMyWndではない場合、クラス名、テーブルを適宜マニュアルで修正してください\r\n//BEGIN_SDIMSG(");
    File.ReplaceAll("END_WNDMSG", "//END_WNDMSG\r\nEND_CMDTABLE");
    //CDLGベースのモードレスダイアログの場合
    File.ReplaceAll("BEGIN_MODELESSDLGMSG(", "BEGIN_CMDTABLE(CMyWnd)\t//クラス名がCMyWndではない場合、クラス名、テーブルを適宜マニュアルで修正してください\r\n//BEGIN_MODELESSDLGMSG(");
    //CDLGベースのモーダルダイアログの場合
    File.ReplaceAll("BEGIN_MODALDLGMSG(", "BEGIN_CMDTABLE(CMyWnd)\t//クラス名がCMyWndではない場合、クラス名、テーブルを適宜マニュアルで修正してください\r\n//BEGIN_MODALDLGMSG(");
    File.ReplaceAll(" : public CDLG {\r\npublic:", " : public CDLG {\r\npublic:\r\n\tCMDTABLE\t\t//OnCommand()関数宣言");
    //ダイアログ共通
    File.ReplaceAll("END_DLGMSG", "//END_DLGMSG\r\nEND_CMDTABLE");
    *m_Ext = NULL;
    fn = m_PathName;
    *m_Ext = '.';
    fn = fn + "_ECC.h";
    File.ToFile(fn.ToChar());
    return TRUE;
}

bool CMyWnd::ModifyProcH() {

    CSTR File, fn(m_PrjtName);
    File.FromFile(m_PathName); 
    fn = fn + "Proc.h を処理します。";
    SendItemMsg(IDC_LISTBOX, LB_ADDSTRING, 0, (LPARAM)fn.ToChar());
    File.ReplaceAll("BCCSkelton", "ECCSkelton");
    File.ReplaceAll(" \"", " L\"");                //[ "]→[ L"]
    File.ReplaceAll(" \'", " L\'");                //[ ']→[ L']
    File.ReplaceAll("char", "WCHAR");            //[char]→[WCHAR]
    File.ReplaceAll("LPSTR", "LPWSTR");            //[LPSTR]→[LPWSTR]
    File.ReplaceAll("LPCSTR", "LPCWSTR");        //[LPCSTR]→[LPCWSTR]
    ///////////////////////////////////////////////////////////////////
    //既にプログラムを書き始めている場合の為に以下オプションを用意した。
    //多くのWin32API関数名はUNICODEが定義されていれば、ワイド文字にWを
    //付けてくれる為、特に変換しなくてもよい。
    //なお、自分用にもっと増やしたい場合は↓のリストに追加すればよい。
    ///////////////////////////////////////////////////////////////////

    if(m_Option) {
        //Win32 API関数
        File.ReplaceAll("MessageBox", "MessageBoxW");    //[MessageBox]→[MessageBoxW]
        File.ReplaceAll("SendMessage", "SendMessageW");    //[SendMessage]→[SendMessageW]
        File.ReplaceAll("SendDlgItemMessage", "SendDlgItemMessageW");    //[SendDlgItemMessage]→[SendDlgItemMessageW]
        File.ReplaceAll("lstrcmp", "lstrcmpW");            //[lstrcmp]→[lstrcmpW]
        File.ReplaceAll("lstrcpy", "lstrcpyW");            //[lstrcpy]→[lstrcpyW]
        File.ReplaceAll("lstrcat", "lstrcatW");            //[lstrcat]→[lstrcatW]
        File.ReplaceAll("lstrlen", "lstrlenW");            //[lstrlen]→[lstrlenW]
        File.ReplaceAll("wsprintf", "wsprintfW");        //[wsprintf]→[wsprintfW]
        //C標準ライブラリ―
        File.ReplaceAll("strstr", "StrStrW");            //[strstr]→[StrStrW] Shellの関数を優先した
        //File.ReplaceAll("strstr", "wcsstr");            //[strstr]→[wcsstr]
        File.ReplaceAll("strcmp", "wcscmp");            //[strcmp]→[wcscmp]
        File.ReplaceAll("strcpy", "wcscpy");            //[strcpy]→[wcscpy]
        File.ReplaceAll("strcat", "wcscat");            //[strcat]→[wcscat]
        File.ReplaceAll("strlen", "wcslen");            //[strlen]→[wcslen]

//(解説:C標準ライブラリーはUNICODEフラグで勝手に変換してくれないと思うので、これらを使われ高た方は変換が必要です。なお、私はstrstrのワイド文字版はStrStrWというウィンドウズの関数を使う趣味なので、敢えてこうしています。)

    }
    *m_Ext = NULL;
    fn = m_PathName;
    *m_Ext = '.';
    fn = fn + "Proc_ECC.h";
    File.ToFile(fn.ToChar());
    return TRUE;
}

 

と、拍子抜けする位簡単な処理でした。

SkeltonWizardで作ったばかりのファイルであればオプションを入れずに100%変換されます。なお、上記したように「メインウィンドウ以外のダイアログ」を使う場合については前々回に書いた以下注に注意してください。

 

注:RTWEditorがそうですが、SkeltonWizardは親ウィンドウをCMyWndクラスとしてコーディングしますので、親ウィンドウは自動で変換できますが、ユーザーが定義するその他のダイアログはそれぞれの任意のダイアログIDを基にクラス名が作られるので、これらはユーザーがマニュアルで対応する必要があります。

例:IDD_INPUT→INPUTDLGクラス、IDD_VERSION→VERSIONDLGクラス

但し、その場合も、

(1)テーブル開始行のクラス名の修正

   例:BEGIN_CMDTABLE(CMyWnd)→BEGIN_CMDTABLE(VERSIONDLG)

(2)テーブルのコントロールコマンド修正

   例:ON_COMMAND(versiondlg, IDC_VERTXT, OnVertxt())→ON(IDC_VERTXT, OnVertxt())

だけと、いたって簡便です。

 

メインダイアログ一個のシンプルなソフトなのでサクッといきますか?

 

【BCC2ECC.rc】

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

//----------------------------------
// ダイアログ (IDD_MAIN)
//----------------------------------
IDD_MAIN DIALOG DISCARDABLE 0, 0, 270, 91
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | WS_DLGFRAME | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | DS_SETFONT | DS_CENTER
CAPTION "BCC2ECC"
FONT 8, "MS 明朝"
{
 CONTROL "選択", IDC_SEL, "BUTTON", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 237, 3, 27, 15
 CONTROL "特定関数も対象にする", IDC_OPTION, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX, 75, 69, 120, 15
 CONTROL "変換", IDOK, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, 213, 69, 45, 15
 CONTROL "終了", IDNO, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, 9, 69, 45, 15
 CONTROL "プロジェクト名", 0, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY | SS_LEFT, 3, 6, 63, 12
 CONTROL "", IDC_EDIT, "EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | ES_READONLY | ES_LEFT, 69, 3, 159, 15, WS_EX_CLIENTEDGE
 CONTROL "", IDC_LISTBOX, "LISTBOX", WS_CHILD | WS_VISIBLE | WS_VSCROLL, 6, 27, 258, 54, WS_EX_CLIENTEDGE
}

//--------------------------
// イメージ(IDI_ICON)
//--------------------------
IDI_ICON    ICON    DISCARDABLE    "Icon.ico"
 

SkelonWizardが排出するbdpファイルを選択するためのEDITコントロールとボタン、現在度のファイルを処理しているのかを示すリストボックス、特定関数の変換処理を行うか否かを決定するチェックボックスと最後に変換処理を行うボタンと終了ボタンだけのダイアログです。

 

【BCC2ECC.cpp】

////////////////////////////////////////////////////
// BCC2ECC.cpp
// このソフトはbcc32(bcc55)でコンパイル
// できますが、正常に動作しない可能性があります。
// bcc32c(bcc102)を使ってください。
// 詳しくは↓
//https://ameblo.jp/ysama2021/entry-12763568154.html
//Copyright (c) 09/06/2022 by BCCSkelton
////////////////////////////////////////////////////
#include    "BCC2ECC.h"
#include    "BCC2ECCProc.h"

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

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

    //モードレスダイアログを作成Create(hParent, DlgName, DlgProc);
    if(!BCC2ECC.Create(NULL, hInstance, "IDD_MAIN", ModelessProc))
        return 0L;

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

SkeltonWizardの排出したコード通りのファイルです。なお、前回書いた

【旧bcc32の謎】誰か分かる人はいますか?

があるので、コメントで明記しておきました。

 

【BCC2ECC.h】

//////////////////////////////////////////
// BCC2ECC.h
// Copyright (c) 09/06/2022 by BCCSkelton
//////////////////////////////////////////
//BCCSkeltonのヘッダー-これに必要なヘッダーが入っている
#include    "BCCSkelton.h"
//リソースIDのヘッダー
#include    "ResBCC2ECC.h"

/////////////////////////////////////////////////////////////////////
//CMyWndクラスをCDLGクラスから派生させ、メッセージ用の関数を宣言する
/////////////////////////////////////////////////////////////////////
class CMyWnd : public CDLG
{
    public:    //メンバー変数
    //ファイル関連外部変数
    char m_PathName[MAX_PATH];    //フルパスプロジェクトファイル名
    char* m_Ext;                //フルパスプロジェクトファイル名の'.'の位置
    char m_PrjtName[MAX_PATH];    //プロジェクト名のみ
    bool m_Option;

    public:    //以下はコールバック関数マクロと関連している
    //2重起動防止用のMutex用ID名称
    CMyWnd(char* UName) : CDLG(UName) {}
    //メニュー項目、ダイアログコントロール関連
    bool OnSel();
    bool OnIdno();
    bool OnIdok();
    //ウィンドウメッセージ関連
    bool OnClose(WPARAM, LPARAM);
    bool OnDestroy(WPARAM, LPARAM);
    //ユーザー定義関数
    void Init();
    bool ModifyRC();
    bool ModifyCPP();
    bool ModifyH();
    bool ModifyProcH();

};

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

BEGIN_MODELESSDLGMSG(ModelessProc, BCC2ECC)    //コールバック関数名は主ウィンドウの場合ModelessProcにしている
    //メニュー項目、ダイアログコントロール関連
    ON_COMMAND(BCC2ECC, IDC_SEL, OnSel())
    ON_COMMAND(BCC2ECC, IDOK, OnIdok())
    ON_COMMAND(BCC2ECC, IDNO, OnIdno())
    //ウィンドウメッセージ関連
    //自動的にダイアログ作成時にOnInit()、終了時にOnClose()を呼びます
    ON_DESTROY(BCC2ECC)
END_DLGMSG

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

これもほぼSkeltonWizardのコード通りです。bdpファイルを選択して開くのでCMNDLGクラスのインスタンスを追加しています。

独自コードはメンバー変数を4つ(↑赤字)と変数、ユーザー定義関数としてコントロールの初期化関数、4つのファイルの処理関数(↑赤字)をメンバー関数として追加しています。

 

これらの追加コードで何をしているのかは、Proc.hの解説で行います。