前回まで、久々にBCCSkeltonでC++プログラムを書いたら、今度はC#でも書きたくなりました。相変わらずネタは無いですが、前回学んだ「Microsoft Learnを見ているとネタになる」という知恵を実践します。

 

最初に頭に浮かんだのは、

 

イメージリスト

 

です。私が最初にイメージリストを知り、使ったのはBCCForm and BCCSkeltonのツールであるTBEditorで、ツールバービットマップをドラッグして選択するところ、例えばツールバー表示の

「ドア」のビットマップを右の実サイズ編集エリアへドラッグ(マウスアイコンは隠れています)して落とし、

左の拡大編集エリアで編集するところが気に入っていました。

 

このようなことが出来ないかな、と思ったのですが、C#ではそのようなメソッド()は無いですね。残念!

:ImageListクラスはFormsから派生しているようなので、自分でWM_L(R)BUTTONDOWN、WM_MOUSEMOVE、WM_WM_L(R)BUTTONUPのイベントハンドラーを使って作る、ということも可能かも。しかし、何か労多くして益少なし、の感がありますね。

 

何れにしても、ImageListクラスの英文サンプルをネタにしてみようと思い、MSCompAssでコンパイルすると、

このように動くことは動くのですが、

 

(1)不要なコンポーネントのDLLを参照し、宣言している。

(2)コントロールの宣言がコンストラクターに置かれたり、InitializeComponentに置かれたりしてバラバラ。

(3)コントロールの二重定義もある。

(4)コントロール名(label3、label5)をそのまま初期表示している。(↑参照)

(5)イメージを削除しても削除したイメージが表示され続ける。(表示に段差がありますね↓参照)

(6)イメージを削除し、リストボックスが未選択になって、再度削除ボタンを押すと落ちる。(↓参照)

(7)ウィンドウサイズは可変ですが、コントロールは微動だにしません。

等々、(突っ込みどころ満載の)大分手抜きをしたっぽいコードになっています。(ネタとしては最高ですね!)

 

ということで、

 

このサンプルコードとプログラムを、

 

(1)日本語化

(2)コントロールのレイアウトを変更してアンカーを効かせる。

(3)雑なコーディングをきちんと統一化する。(日本語コメントも付けます。)

 

という感じでブラッシュアップしましょう。

 

前回までにResourceListプロジェクトのファイル説明を行いましたが、最後にこのウィンドウプログラムの動作を決定するResourceListProc.hについて解説します。手作業で追加修正した所を赤字で示します。

 

【ResourceListProc.h】

//////////////////////////////////////////
// ResourceListProc.h
// Copyright (c) 08/11/2024 by BCCSkelton
//////////////////////////////////////////

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

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

//解説:これはウィンドウが作られる際に呼び出される関数で、初期化等を行います。

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

    //ツールバー登録-Init(hWnd, hIinstance, ID, (以下省略可)Style)
    TBar.Init(m_hWnd, m_hInstance, TOOLBAR);
    //ツールバーボタン用カスタムビットマップ追加
    TBar.AddBmp(m_hInstance, MAKEINTRESOURCE(IDI_TOOLBAR), 4);
    //ツールバーボタン追加
    TBBUTTON tbb[6];
    ZeroMemory(tbb, sizeof(tbb));
    tbb[0].iBitmap = TBar.m_id + 0;
    tbb[0].fsState = TBSTATE_ENABLED;
    tbb[0].fsStyle = TBSTYLE_BUTTON;
    tbb[0].idCommand = IDM_OPEN;
    tbb[1].iBitmap = TBar.m_id + 1;
    tbb[1].fsStyle = TBSTYLE_BUTTON;
    tbb[1].idCommand = IDM_SAVE;
    tbb[2].fsStyle = TBSTYLE_SEP;    //セパレーター
    tbb[3].iBitmap = TBar.m_id + 2;
    tbb[3].fsState = TBSTATE_ENABLED;
    tbb[3].fsStyle = TBSTYLE_BUTTON;
    tbb[3].idCommand = IDM_EXIT;
    tbb[4].fsStyle = TBSTYLE_SEP;    //セパレーター
    tbb[5].iBitmap = TBar.m_id + 3;
    tbb[5].fsState = TBSTATE_ENABLED;
    tbb[5].fsStyle = TBSTYLE_BUTTON;
    tbb[5].idCommand = IDM_VERSION;
    TBar.AddButtons(6, tbb);

    //ステータスバー登録-Init(hWnd, hIinstance, ID, (以下省略可)Style)
    SBar.Init(m_hWnd, m_hInstance, STATUSBAR);
    //ステータスバー区画設定
    int sec[2] = {120, -1};
    SBar.SetSection(2, sec);
    //ステータスバー文字列設定
    SBar.SetText(0, "ResourceList Ver 1.0");
    SBar.SetText(1, "(ファイル名)");

    //ドラッグアンドドロップの受付開始-解説:ウィンドウとその子ウィンドウ(コントロール)にファイルをドロップされるとメッセージを出します。
    DragAcceptFiles(m_hWnd, TRUE);

    //エディットコントロールの作成-解説:SDIウィンドウに直接文字を表示せず、EDITコントロールを貼って、それに表示させます。
    Edit.Init("EDIT", m_hInstance);    //解説:CCTRLクラスはどのようなコントロールも対応しますが、ここでは"EDIT"クラスにしています。
    Edit.Create("(リソース情報データを表示します)",    //解説:コントロールを生成、表示します。
                    WS_CHILD | WS_VISIBLE | WS_BORDER | WS_HSCROLL | WS_VSCROLL |
                    ES_AUTOHSCROLL | ES_AUTOVSCROLL | ES_MULTILINE | ES_READONLY,
                    WS_EX_CLIENTEDGE, m_hWnd, IDC_EDIT, 0, 0, 0, 0);    
//解説:この引数はWinAPIを参照して下さい。
    Edit.SetFont(11, "MS 明朝");    //解説:フォントを設定しています。

    //データファイルで起動された場合、ファイルをオープンする
    if(g_ByFile) {      //解説:グローバル変数(bool)のフラグを使います。(ResourceList.cpp参照)
        g_FileName = Arg.v(1);    //第1パラメーターのファイル名(のみ)-解説:複数のファイルの場合は最初のものになります。
        OnOpen();    //解説:OnOpen関数のg_ByFileフラグが真の所を参照して下さい。
    }
    return TRUE;
}

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

    //ツールバーからのツールチップ情報取得 -解説:以下はツールバーチップの定番処理です。私はコピペで使ってます。
    if(((LPNMHDR)lParam)->code == TTN_NEEDTEXT) {
        static LPTOOLTIPTEXT lptip;
        lptip = (LPTOOLTIPTEXT)lParam;
        switch (lptip->hdr.idFrom) {
        case IDM_OPEN:
            lptip->lpszText = "実行可能ファイルを開く";
            break;
        case IDM_SAVE:
            lptip->lpszText = "リソース情報ファイルの保存";
            break;
        case IDM_EXIT:
            lptip->lpszText = "終了";
            break;
        case IDM_VERSION:
            lptip->lpszText = "バージョン情報";
            break;
        default:
            return FALSE;
        }
        return TRUE;
    }
    return FALSE;

}

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

    //ツールバー再設定
    TBar.AutoSize();
    //ステータスバー再設定
    SBar.AutoSize();
    //EDITコントロールをクライアントエリアに設定-解説:SDIウィンドウサイズが変更された際にそれに合わせてEDITコントロールのサイズを変更します。
    RECT rec;
    GetWindowRect(TBar.GetHandle(), &rec);    //ツールバー高さ取得
    int top = (rec.bottom - rec.top);
    int height = HIWORD(lParam) - top;
    GetWindowRect(SBar.GetHandle(), &rec);    //ステータスバー高さ取得
    height -= (rec.bottom - rec.top);
    MoveWindow(GetDlgItem(m_hWnd, IDC_EDIT), 0, top, LOWORD(lParam), height, TRUE);

    return TRUE;
}

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

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

//解説:以下はResourceList.hと同じく、手書きで追加した部分です。これも定番処理なのでコピペで使いました。
bool CMyWnd::OnDropFiles(WPARAM wParam, LPARAM lParam) {

    
//典型的なドラッグアンドドロップ処理
    static char lpszFn[MAX_PATH];                //ファイル名用バッファ
    HDROP hDrop = (HDROP)wParam;                 //HDROPを取得
    DragQueryFile(hDrop, 0, lpszFn, MAX_PATH);    //最初のファイル名を取得(解説:複数ファイル対応は止めました。)
    g_FileName =lpszFn;                            //g_FileNameでファイルパス、名(のみ)を渡す
    g_ByFile = TRUE;
    OnOpen();
    DragFinish(hDrop);                            
//ドラッグアンドドロップの終了処理
    return TRUE;
}

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

    
//典型的なウィンドウのサイズ制限処理
    MINMAXINFO *pmmi;
    pmmi = (MINMAXINFO*)lParam;
    pmmi->ptMinTrackSize.x = 496;    
//クライアントエリア480 + 16(解説:16は左右ウィンドウ枠幅)
    pmmi->ptMinTrackSize.y = 381;    //クライアントエリア320 + 61(解説:61はタイトル枠と上下ウィンドウ枠幅)

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

/////////////////////////////////
//主ウィンドウCMyWndの関数の定義
//メニュー項目、コントロール関数
/////////////////////////////////

bool CMyWnd::OnOpen() {

    //ファイル、フィルター用変数
    char* ptFile;
    char* ptFilter = "モジュールファイル(exe, dll)\0*.exe;*.dll\0すべてのファイル\0*.*\0\0";

    //ファイル名の取得
    if(g_ByFile)        //ファイル引数付き起動、Drag and Dropまたはメニューの場合
        g_ByFile = FALSE;        //初期状態に戻す
    else {                //メニューやコントロールの場合
        //選択されているアイテムの文字列を取得し、これでフィルターを作成
        ptFile = cmndlg.GetFileName(m_hWnd, ptFilter, TRUE);
        if(!ptFile) {
            MessageBox(m_hWnd, "ファイルが選択されませんでした", "エラー", MB_OK | MB_ICONSTOP);
            return FALSE;
        }
        else {
            g_FileName = ptFile;
        }
    }
    SBar.SetText(1, g_FileName.ToChar());
    SBar.SendMsg(SB_SETTIPTEXT, 1, (LPARAM)g_FileName.ToChar());    
//ToolTipをつける
    //リソースリストの設定と表示
    ResList.BeginResList(g_FileName.ToChar());        //新しい実行可能ファイルを再度開く(解説:CRESLISTクラスインスタンスです。)
    SendMessage(Edit.GetHandle(), WM_SETTEXT, 0, (LPARAM)ResList.GetData());    //解説:EDITコントロールに文字列を表示させます。
    //「リソース情報ファイルの保存」を有効にする
    HMENU hMenu = GetMenu(ResourceList.GetHandle());    //メニューハンドルの取得
    HMENU hFileMenu = GetSubMenu(hMenu, 0);
    EnableMenuItem(hFileMenu, IDM_SAVE, MF_BYCOMMAND | MF_ENABLED);
    //EnableMenuItem(hFileMenu, IDM_SAVE, MF_BYCOMMAND | (EnableFlag ? MF_ENABLED : MF_GRAYED));
    
//ツールバーボタンを有効にする
    SendMessage(TBar.GetHandle(), TB_ENABLEBUTTON, IDM_SAVE, MAKELONG(TRUE, 0));
//解説:初期状態でメニューとツールバーボタンの「ファイル保存」を不可にしているので、それを有効化しています。

    return TRUE;
}

bool CMyWnd::OnSave() {

    ResList.SaveList();    //解説:単にリソース情報をファイルとして出力するだけです。
    return TRUE;
}

bool CMyWnd::OnExit() {

    SendMsg(WM_CLOSE, 0, 0);    //解説:ウィンドウをクローズします。
    return TRUE;
}

bool CMyWnd::OnVersion() {

    //IDD_VERSIONダイアログの表示
    versiondlg.DoModal(m_hWnd, "IDD_VERSION", versiondlgProc);    //解説:モーダルダイアログを開きます。
    return TRUE;
}

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

bool VERSIONDLG::OnIdok() {

    EndModal(TRUE);    //解説:モーダルダイアログを閉じます。
    return TRUE;
}

 

これでResourceListウィンドウプログラムが出来ました。

 

Microsoft Learnに出ていたサンプルを使い、先ずコンソールプログラムで動くようにし、クラス化し、文字列データ化し、数値表示を名詞表示にし、最後にウィンドウプログラムにしてみましたが、いかがだったでしょうか?

 

基本的に簡単でシンプルなプログラムでしたが、久々のBCCSkeltonプログラミングだったのでちょっと時間がかかりましたが、楽しかったです。

 

今回は、前回SkeltonWizardで自動作成したファイルの内、比較的簡単に仕上げられるResourceList.cppとResourceList.hを完成させましょう。()以下必要に応じ、プログラム中に解説:をいれますが、修正、追加部分は赤字で書きます。

:現在のファイルはBCCSkelton用のANSIベースになっています。これをUNICODE(UTF-16)にして使いたい場合には、ECCSkeltonのサンプルにあるBCC2ECCを使ってプロジェクトファイルをUNICODE用に変換してください。また、CRESLIST.h#define UNICODE 1(またはTRUE)としてください。この場合、以下の赤字で書かれる追加、修正コードはUICODE用に変えてください。(char→WCHAR、""→L""、<関数>→<関数W> etc)GachGoodの実行ファイル形式も「WIndowsプログラム(Unicode)」を指定してください。

 

【ResourceList.cpp】

プログラムがファイルをドロップされて起動した場合の処理とウィンドウ左上のアイコンを設定しています。

//////////////////////////////////////////
// ResourceList.cpp
//Copyright (c) 08/11/2024 by BCCSkelton
//////////////////////////////////////////

#include    "ResourceList.h"
#include    "ResourceListProc.h"

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

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

    //ファイル引数付きで起動する場合(g_ByFileフラグを立てる)
    if(Arg.c() > 1)
        g_ByFile = TRUE;

    //解説:起動コマンド(文字列)を取得するCARDクラスのインスタンスArgを使い、引数があるか否かを確認し、あればbool型のグローバル変数g_ByFileを立てます。(その理由はResourcelistproc.hの解説で書きます。)

    //2重起動防止(解説:複数起動して差し支えなければこのブロックを総て削除してください。)
    if(!ResourceList.IsOnlyOne()) {
        HWND hWnd = FindWindow("MainWnd", "ResourceList");
        if(IsIconic(hWnd))
            ShowWindow(hWnd, SW_RESTORE);
        SetForegroundWindow(hWnd);
        return 0L;
    }

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

    ResourceList.Init("MainWnd", hInstance, SDIPROC, "IDM_MAIN", MAKEINTRESOURCE(IDI_ICON));
    //解説:無くても動きますが、赤字を追加してシステムアイコンをウィンドウの左上に表示させます。


    //ウィンドウ作成と表示-Create(WindowTitle, (以下省略可)Style,
    //                        ExStyle, hParent, hMenu, x, y, w, h)

    if(!ResourceList.Create("ResourceList"))
        return 0L;

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

 

【ResourceList.h】

リソース情報出力用CRESLISTクラスの定義を入れたヘッダーファイルをインクルードし、ドラッグアンドドロップと最小ウィンドウサイズ制限用のメッセージテーブルの追加と関数の定義を行っています。又、プログラムで利用するクラスオブジェクトインスタンスの宣言外部変数の宣言を行っています。

//////////////////////////////////////////
// ResourceList.h
// Copyright (c) 08/11/2024 by BCCSkelton
//////////////////////////////////////////
//BCCSkeltonのヘッダー-これに必要なヘッダーが入っている

#include    "BCCSkelton.h"
//リソースIDのヘッダー
#include    "ResResourceList.h"
//CRESLISTクラスヘッダー
#include    "CRESLIST.h"    //解説:作成したCRESLIST.hファイルを同じフォールダーに入れてインクルードします。

/////////////////////////////////////////////////////////////////////
//CMyWndクラスをCSDIクラスから派生させ、メッセージ用の関数を宣言する
/////////////////////////////////////////////////////////////////////

class CMyWnd : public CSDI
{
public:    //以下はコールバック関数マクロと関連している
    //2重起動防止用のMutex用ID名称
    CMyWnd(char* UName) : CSDI(UName) {}
    //メニュー項目、ダイアログコントロール関連
    bool OnOpen();
    bool OnSave();
    bool OnExit();
    bool OnVersion();
    //ウィンドウメッセージ関連
    bool OnCreate(WPARAM, LPARAM);
    bool OnNotify(WPARAM, LPARAM);
    bool OnSize(WPARAM, LPARAM);
    bool OnClose(WPARAM, LPARAM);
    bool OnDropFiles(WPARAM, LPARAM);    //解説:ドラッグアンドドロップ用の関数
    bool OnMinMax(WPARAM, LPARAM);    //解説:最小ウィンドウサイズ制限用の関数
};

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

CMyWnd ResourceList("ResourceList");    //ウィンドウクラスインスタンスの生成

BEGIN_SDIMSG(ResourceList)    //ダイアログと違い、コールバック関数名を特定しない
    //メニュー項目、ダイアログコントロール関連
    ON_COMMAND(ResourceList, IDM_OPEN, OnOpen())
    ON_COMMAND(ResourceList, IDM_SAVE, OnSave())
    ON_COMMAND(ResourceList, IDM_EXIT, OnExit())
    ON_COMMAND(ResourceList, IDM_VERSION, OnVersion())
    //ウィンドウメッセージ関連
    ON_CREATE(ResourceList)
    ON_NOTIFY(ResourceList)
    ON_SIZE(ResourceList)
    ON_CLOSE(ResourceList)
    ON_(ResourceList, WM_DROPFILES, OnDropFiles(wParam, lParam))    //解説:ドラッグアンドドロップ用メッセージ
    ON_(ResourceList, WM_GETMINMAXINFO, OnMinMax(wParam, lParam))    //解説:最小ウィンドウサイズ制限用メッセージ
END_WNDMSG

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

class VERSIONDLG : public CDLG {
public:
    bool OnIdok();
};

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

VERSIONDLG versiondlg;

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

///////////////////
//ツールバーの作成
///////////////////

CTBAR TBar;

///////////////////////
//ステータスバーの作成
///////////////////////

CSBAR SBar;

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

CMNDLG cmndlg;

///////////////////
//Editコントロール
///////////////////

#define    IDC_EDIT    1000
CCTRL Edit;


/////////////////////////////
//コマンドラインクラスの作成
/////////////////////////////

CARG Arg;

/////////////////////////////
//リソースリストクラスの作成
/////////////////////////////

CRESLIST ResList;

//---------------
//グローバル変数
//---------------

bool g_ByFile = FALSE;        //ファイル起動用変数
CSTR g_FileName;            //ファイル名用文字列

 

次回は最終回としてResourceListProc.hの解説を行います。

 

「ええいっ、くそっ!!!」、下書きを間違えて上書きし、データが消えてしまったたたたたた!!!

 

やりなおしだよぉ(泣;)(めげずに書き直しました。)

 

前回はリソースを作成するところまで来ました。今回と次回はこのリソースファイルを基に、BCCForm and BCCSkeltonパッケージのSkeltonWizard.exe使ってウィンドウプログラムのスケルトンを作り、それに必要な修正を行います。

 

今回のプログラムは比較的シンプルなものなので、

メニュー、

バージョンダイアログ、

アイコン、

ツールバービットマップ、

位です。

 

これらをBCCFormで一つに纏め、ResourceList.rcとResResourceList.hファイルを書き出します。

 

【ResourceList.rc】

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

//-------------------------
// メニュー(IDM_MAIN)
//-------------------------
IDM_MAIN MENU DISCARDABLE
{
    POPUP "ファイル(&F)"
    {
        MENUITEM "実行可能ファイルを開く(&O)", IDM_OPEN
        MENUITEM "リソース情報ファイルの保存(&S)", IDM_SAVE, GRAYED    //解説これは訳アリで後で追加しました
        MENUITEM SEPARATOR
        MENUITEM "終了(&X)", IDM_EXIT
    }
    POPUP "ヘルプ(&H)"
    {
        MENUITEM "バージョン情報(&V)", IDM_VERSION
    }
}

//----------------------------------
// ダイアログ (IDD_VERSION)
//----------------------------------
IDD_VERSION DIALOG DISCARDABLE 0, 0, 213, 45
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | DS_MODALFRAME | WS_CAPTION | DS_SETFONT | DS_CENTER
CAPTION "バージョン情報"
FONT 9, "Times New Roman"
{
 CONTROL IDI_ICON, 0, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY | SS_SUNKEN | SS_ICON | SS_CENTERIMAGE, 5, 5, 36, 36, WS_EX_CLIENTEDGE
 CONTROL "ResourceList Ver 1.0\r\n(c) Copyright 2024 by ysama\r\nAll rights reserved", 0, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY | SS_SUNKEN | SS_CENTER, 48, 6, 120, 33, WS_EX_CLIENTEDGE
 CONTROL "OK", IDOK, "BUTTON", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 174, 14, 32, 16
}

//----------------------------------
// ダイアログで使用するイメージ
//----------------------------------
IDI_ICON    ICON    DISCARDABLE    "Icon.ico"

//--------------------------
// イメージ(IDI_TOOLBAR)
//--------------------------
IDI_TOOLBAR    BITMAP    DISCARDABLE    "ResourceList.bmp"

 

【ResResourceList.h】

//-----------------------------------------
//             BCCForm Ver 2.41
//   Header File for Resource Script File
//   Copyright (c) February 2002 by ysama
//-----------------------------------------
//---------------------
//  ダイアログリソース
//---------------------
// ダイアログ IDD_VERSION

//---------------------
//  メニューリソース
//---------------------
// メニュー IDM_MAIN
#define    IDM_OPEN        200
#define    IDM_SAVE        201
#define    IDM_EXIT        202
#define    IDM_VERSION        203

//---------------------
//  イメージリソース
//---------------------
#define    IDI_TOOLBAR        300
#define    IDI_ICON        400
 

次にSkeltonWizard.exeに引数としてResourceList.rcファイルを与え(要すればファイルをドロップする)起動します。

ウィザードの最初のページ(プロパティシート)の項目を↑のように選択し、「次に」ボタンを押します。

第2ページでは「コモンコントロールの...」のチェックボックスを2回クリックして外し、ツールバービットマップとメニュー項目を関連付けます。又ステータスバーの区画数と初期文字列を設定します。

第3ページでは割込み関数のベースとなるウィンドウメッセージとメニュー項目を選択します。メッセージはWM_CREATE、WM_NOTIFY、WM_SIZE、WM_CLOSEを、メニュー項目は「総て選択」にします。

最後のページは内容の確認です。(今回は「WYSWYG」や「CPPファイルを書き換えない」は使いません。)

 

そして「完了」ボタンを押すと、ResourceList.h、ResourceListProc.h、ResourceList.cppの3つのファイルが作られます。

 

このままResourceList.cppとResourceList.rcファイルをBatchGood.exeファイルにドロップし、(オプションの)「詳細」ボタンを押してインクルードファイルにBCCSkelton.hのあるフォールダーを指定し、コンパイルすると、スケルトンウィンドウ(要すれば「ドンガラ」)が完成します。

 

次回は自動作成されたResourceList.cppとResourceList.hに必要なコードを入力して完成させます。

 

何度か書きましたが、BCCForm and BCCSkeltonで開発を行う場合には、先ずターゲットウィンドウをイメージしてリソースを作ることから始めます。

 

今回のResourceListは比較的シンプルな機能なので、ターゲットをメニュー、ツールバー、ステータスバーを備えたSDIウィンドウとし、リソースとしてはメニューとそれに関連付けるツールバービットマップ、

 

バージョン表示ダイアログとそれに表示するシステムアイコンを用意しました。

 

これらのリソースをBCCFormでまとめたリソーススクリプトファイルを"RecourceList.rc"として保存します。この際、各リソースのIDが定数として定義されたResResourceList.hも同時に作成されます。

 

【ResourceList.rc】

//-----------------------------------------
//             BCCForm Ver 2.41
//    An Easy Resource Editor for BCC
//  Copyright (c) February 2002 by ysama
//-----------------------------------------

#include    "ResResourceList.h"

//----------------------------------
// ダイアログ (IDD_VERSION)
//----------------------------------

IDD_VERSION DIALOG DISCARDABLE 0, 0, 213, 45
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | DS_MODALFRAME | WS_CAPTION | DS_SETFONT | DS_CENTER
CAPTION "バージョン情報"
FONT 9, "Times New Roman"
{
 CONTROL IDI_ICON, 0, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY | SS_SUNKEN | SS_ICON | SS_CENTERIMAGE, 5, 5, 36, 36, WS_EX_CLIENTEDGE
 CONTROL "ResourceList Ver 1.0\r\n(c) Copyright 2024 by ysama\r\nAll rights reserved", 0, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY | SS_SUNKEN | SS_CENTER, 48, 6, 120, 33, WS_EX_CLIENTEDGE
 CONTROL "OK", IDOK, "BUTTON", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 174, 14, 32, 16
}

//----------------------------------
// ダイアログで使用するイメージ
//----------------------------------

IDI_ICON    ICON    DISCARDABLE    "Icon.ico"

//-------------------------
// メニュー(IDM_MAIN)
//-------------------------

IDM_MAIN MENU DISCARDABLE
{
    POPUP "ファイル(&F)"
    {
        MENUITEM "実行可能ファイルを開く(&O)", IDM_OPEN
        MENUITEM "リソース情報ファイルの保存(&S)", IDM_SAVE, GRAYED
        MENUITEM SEPARATOR
        MENUITEM "終了(&X)", IDM_EXIT
    }
    POPUP "ヘルプ(&H)"
    {
        MENUITEM "バージョン情報(&V)", IDM_VERSION
    }
}

//--------------------------
// イメージ(IDI_TOOLBAR)
//--------------------------

IDI_TOOLBAR    BITMAP    DISCARDABLE    "ResourceList.bmp"
 

【ResResourceList.h】

//-----------------------------------------
//             BCCForm Ver 2.41
//   Header File for Resource Script File
//   Copyright (c) February 2002 by ysama
//-----------------------------------------
//---------------------
//  ダイアログリソース
//---------------------
// ダイアログ IDD_VERSION

//---------------------
//  メニューリソース
//---------------------
// メニュー IDM_MAIN
#define    IDM_OPEN        200
#define    IDM_SAVE        201
#define    IDM_EXIT        202
#define    IDM_VERSION        203

//---------------------
//  イメージリソース
//---------------------
#define    IDI_TOOLBAR        300
#define    IDI_ICON        400

//---------------------
//  ストリングテーブル
//---------------------

//--------------------
//  アクセラレーター
//--------------------

//------------------
//  ヴァージョン情報
//------------------
 

次回はこのリソースファイルを基に、SkeltonWizardを使ってプロジェクトファイルを作成します。

 

前回リソースリスト(CRESLIST)クラスをファイル出力ベースから文字列ベースに変更したので、後残っていることは、

 

(1)「種類」、「言語」の出力を、人間にとって分かり易い文字出力に変更

(2)コンソール用のコードを削除してヘッダーファイル(CRESLIST.h)とし、BCCSkeltonの器に落とし込む

 

だけです。

 

(1)については、単純にリソースの種類が出ているここと言語が出ているここ(注)をコピペして、構造体にしちゃいましょう。

注:すべてコピーするのは面倒なので、街中の指示標識用言語位にしておきました。そして(2)については"printf(...)"文等のコンソール関連部分を総て削除して、CRESLIST.hというヘッダーファイルを作りました。以下(1)関連は水色で解説します。

 

【CRESLIST.h】

//////////////////////////////////////////////////////////
// CRESLIST.h
// CRESLISTクラス定義ファイル
// 概要:実行可能ファイルのリソースを調べ、一覧を出力する
//////////////////////////////////////////////////////////

#include    <strsafe.h>

#define     RTTMAX    21    //種類テーブル配列数(開発時の暫定値、増やしていただいて結構です。)
#define     LTMAX    6    //言語テーブル配列数(Ditto)

/////////////
//構造体定義
/////////////

typedef struct _RT_TABLE {    //リソースタイプ(RT)-種類テーブル
    TCHAR* macro;
    UINT id;
    TCHAR* name;
} RT_TABLE, *PRT_TABLE;

typedef struct _LOC_TABLE {    
//ローカライゼィション-言語テーブル
    TCHAR* area;
    TCHAR* tag;
    UINT id_hex;
    UINT id;
} LOC_TABLE, *PLOC_TABLE;


///////////////////////////////
// CRESLISTクラス定義ファイル
// Copyright (c) August, 2024
//        By Ysama
///////////////////////////////

class CRESLIST
{
private:
    HMODULE m_hExe;                            //EXEファイルのハンドル
    TCHAR m_ResFileName[MAX_PATH] = {'\0'};    //リソースファイル名(拡張子前FileName + "_resinfo.txt")
    TCHAR* m_Data;                            //リソースリストのデータポインター
    size_t m_DataLen;                        //m_Dataの長さ
    RT_TABLE m_rtt[RTTMAX] = {
        
//    マクロ名        整数ID                        説明
        {"RT_CURSOR",        1,                            "ハードウェア依存カーソル"},
        {"RT_BITMAP",        2,                            "ビットマップ"},
        {"RT_ICON",            3,                            "ハードウェア依存するアイコン"},
        {"RT_MENU",            4,                            "メニュー"},
        {"RT_DIALOG",        5,                            "ダイアログボックス"},
        {"RT_STRING",        6,                            "ストリングテーブル"},
        {"RT_FONTDIR",        7,                            "フォントディレクトリ"},
        {"RT_FONT",            8,                            "フォント"},
        {"RT_ACCELERATOR",    9,                            "アクセラレータ"},
        {"RT_RCDATA",        10,                            "アプリケーション定義リソース"},
        {"RT_MESSAGETABLE",    11,                            "メッセージテーブル"},
        {"RT_VERSION",        16,                            "バージョン"},
        {"RT_DLGINCLUDE",    17,                            "文字列関連付け"},
        {"RT_PLUGPLAY",        19,                            "プラグアンドプレイ"},
        {"RT_VXD",            20,                            "Vxd"},
        {"RT_ANICURSOR",    21,                            "アニメーションカーソル"},
        {"RT_ANIICON",        22,                            "アニメーションアイコン"},
        {"RT_HTML",            23,                            "HTML"},
        {"RT_MANIFEST",        24,                            "アセンブリ マニフェスト"},
        {"RT_GROUP_CURSOR",    (ULONG_PTR(RT_CURSOR) + 11),"ハードウェア非依存カーソル"},
        {"RT_GROUP_ICON",    (ULONG_PTR(RT_ICON) + 11),    "ハードウェア非依存アイコン"}
    };
    LOC_TABLE m_lt[LTMAX] = {
    
//    言語/地域                    言語/地域タグ    言語/地域ID        言語/地域10進ID
        {"中国語 (繁体字、台湾)",    "zh-TW",        0x0404,            1028},
        {"英語 (米国)",                "en-US",        0x0409,            1033},
        {"日本語 (日本)",            "ja-JP",        0x0411,            1041},
        {"韓国語 (韓国)",            "ko-KR",        0x0412,            1042},
        {"中国語 (簡体字、中国)",    "zh-CN",        0x0804,            2052},
        {"英語 (英国)",                "en-GB",        0x0809,            2057}

    };
public:
    CRESLIST();                        //コンストラクター1
    CRESLIST(LPCTSTR);                //コンストラクター2
    ~CRESLIST();                    //デストラクター
    void Init();                    //初期化処理
    bool BeginResList(LPCTSTR);        //実行可能ファイル名を与えて開始
    bool AddData(LPCTSTR, size_t);    //m_Dataに長さsize_tの文字列を追加する
    LPCTSTR GetData() {return m_Data;}    //m_Dataをコピーする(呼び出し側でdelete [] destが必須)
    bool SaveList();                //現在開いているファイルのリソースリストを保存する
    static bool EnumTypesFunc(HMODULE, LPCTSTR, LONG);                    //リソース種類取得用コールバック関数
    static bool EnumNamesFunc(HMODULE, LPCTSTR, LPCTSTR, LONG);            //リソース名取得用コールバック関数
    static bool EnumLangsFunc(HMODULE, LPCTSTR, LPCTSTR, WORD, LONG);    //リソース言語取得用コールバック関数
};
 

//コンストラクター1
//コンストラクター2
//デストラクター
//初期化処理
//実行可能ファイル名を与えて開始
bool CRESLIST::BeginResList(LPCTSTR FileName) {
//m_Dataに文字列を追加する
//現在開いているファイルのリソースリストを保存する

 

//ここまでは変更がないため、前回を参照ください。
 

//リソース種類取得用コールバック関数EnumTypesFunc(HMODULE, LPCTSTR, LONG)
bool CRESLIST::EnumTypesFunc(HMODULE hModule, LPCTSTR lpType, LONG lParam) {

    TCHAR szBuffer[MAX_PATH] = {'\0'};    //infoファイル用プリントバッファ
    size_t cbString;                    //szBufferの文字列長
    HRESULT hResult;                    //結果判定用変数

    //szBufferにリソース種類を書き込む
    //種類は文字列または符号なし整数であり、事前にテストを行う

    if(!IS_INTRESOURCE(lpType)) {    //解説:リソースが文字列の場合はそのまま表示します。
        hResult = StringCchPrintf(szBuffer, MAX_PATH, TEXT("種類:%s\r\n"), lpType);
        if(FAILED(hResult))
            return FALSE;
    }
    else    //解説:リソース種類が整数の場合は、該当文字列を検索して表示し、該当が無い場合は数字を表示します。
    {
        //m_rrt配列内リソース種類検索
        for(int i = 0; i < RTTMAX; i++) {    //解説:リソーステーブルのIDを検索します。
            if((UINT)lpType == ((CRESLIST*)lParam)->m_rtt[i].id) {
                StringCchCopy(szBuffer, MAX_PATH, TEXT("種類:"));
                StringCchCat(szBuffer, MAX_PATH, ((CRESLIST*)lParam)->m_rtt[i].name);
                StringCchCat(szBuffer, MAX_PATH, TEXT("\r\n"));
                break;
            }
        }
        if(!*szBuffer) {    
//szBuffer[0]が初期値(NULL)の場合(解説:即ち該当がない場合)
            hResult = StringCchPrintf(szBuffer, MAX_PATH, TEXT("種類:%u\r\n"), (USHORT)lpType);
            if(FAILED(hResult))
                return FALSE;
        }

    }
    hResult = StringCchLength(szBuffer, MAX_PATH, &cbString);
    if(FAILED(hResult))
        return FALSE;
    //m_Dataの更新
    ((CRESLIST*)lParam)->AddData(szBuffer, cbString);
    //同種のリソースを総て検索する
    EnumResourceNames(hModule, lpType, (ENUMRESNAMEPROC)EnumNamesFunc, lParam);
    return TRUE;
}

//リソース名取得用コールバック関数EnumNamesFunc(HMODULE, LPCSTR, LPCSTR, LONG)
bool CRESLIST::EnumNamesFunc(HMODULE hModule, LPCTSTR lpType, LPCTSTR lpName, LONG lParam) {

    TCHAR szBuffer[MAX_PATH] = {'\0'};    //infoファイル用プリントバッファ
    size_t cbString;                    //szBufferの文字列長
    HRESULT hResult;                    //結果判定用変数

    //szBufferにリソース名を書き込む
    //リソース名は文字列または符号なし整数であり、事前にテストを行う

    if(!IS_INTRESOURCE(lpName)) {
        hResult = StringCchPrintf(szBuffer, MAX_PATH, TEXT("\tリソース名:%s\r\n"), lpName);
        if(FAILED(hResult))
            return FALSE;
    }
    else
    {
        hResult = StringCchPrintf(szBuffer, MAX_PATH, TEXT("\tリソース名:%s\r\n"), (USHORT)lpName);
        if(FAILED(hResult))
            return FALSE;
    }
    hResult = StringCchLength(szBuffer, MAX_PATH, &cbString);
    if(FAILED(hResult))
        return FALSE;
    //m_Dataの更新
    ((CRESLIST*)lParam)->AddData(szBuffer, cbString);
    //同種のリソースの言語を総て検索する
    EnumResourceLanguages(hModule, lpType, lpName, (ENUMRESLANGPROC)EnumLangsFunc, lParam);
    return TRUE;
}

//リソース言語取得用コールバック関数EnumLangsFunc(HMODULE, LPCTSTR, LPCTSTR, WORD, LONG)
bool CRESLIST::EnumLangsFunc(HMODULE hModule, LPCTSTR lpType, LPCTSTR lpName, WORD wLang, LONG lParam) {

    HRSRC hResInfo;                        //リソース情報ハンドル
    TCHAR szBuffer[MAX_PATH] = {'\0'};    //infoファイル用プリントバッファ
    size_t cbString;                    //szBufferの文字列長
    HRESULT hResult;                    //結果判定用変数

    hResInfo = FindResourceEx(hModule, lpType, lpName, wLang);
    //szBufferへリソース情報を書き込む
    //m_lt配列内リソース種類検索
    for(int i = 0; i < LTMAX; i++) {    //解説:言語テーブルのIDを検索します。
        if((UINT)wLang == ((CRESLIST*)lParam)->m_lt[i].id) {
            StringCchCopy(szBuffer, MAX_PATH, TEXT("\t\tリソース言語: "));
            StringCchCat(szBuffer, MAX_PATH, ((CRESLIST*)lParam)->m_lt[i].area);
            StringCchCat(szBuffer, MAX_PATH, TEXT("\r\n"));
            break;
        }
    }
    if(!*szBuffer) {        
//szBuffer[0]が初期値(NULL)の場合(解説:即ち該当がない場合)
        hResult = StringCchPrintf(szBuffer, MAX_PATH, TEXT("\t\tリソース言語: %u\r\n"), (USHORT) wLang);
        if(FAILED(hResult))
            return FALSE;
    }

    hResult = StringCchLength(szBuffer, MAX_PATH, &cbString);
    if(FAILED(hResult))
        return FALSE;
    //m_Dataの更新
    ((CRESLIST*)lParam)->AddData(szBuffer, cbString);
    //リソースハンドルとサイズをバッファーに書き込む
    hResult = StringCchPrintf(szBuffer, MAX_PATH, TEXT("\t\tリソースハンドル:%lx、サイズ:%lu\r\n\r\n"), hResInfo, SizeofResource(hModule, hResInfo));
    if(FAILED(hResult))
        return FALSE;
    hResult = StringCchLength(szBuffer, MAX_PATH, &cbString);
    if (FAILED(hResult))
        return FALSE;
    //m_Dataの更新
    ((CRESLIST*)lParam)->AddData(szBuffer, cbString);
    return TRUE;
}

 

これでウィンドウズプログラムに使えるCRESLISTクラスのヘッダーファイルが出来ました。後はそれをウィンドウの器に入れてやるだけです。

(種類、言語が文字列で表示されている。)

 

次回からはBCCSkelton()版のResourceListの開発手順を示します。

:BCCSkelton版はANSIベースになります。UNICODEベースにしたい場合は、一旦BCCSkeltonのファイルをSkeltonWIzardで作り、その後BCC2ECCユーティリティを使ってECCSkeltonのファイルに変換してください。この話は次回以降また説明します。

 

前回書きましたように、今回は「クラスオブジェクトしたものを、よりポータブルにすべく『ファイル出力』を『文字列データ』に変更します。(ファイル出力ももちろん可能です。)

 

具体的にはResourceList03.cppまでは、

 

「最初にリソース情報ファイルを開き、漸次データをリソース毎に書き込んでゆき、最後にファイルを閉じる」

 

ようにしていますが、これを

 

「最初に文字列ポインターを作り、それに文字列を追加していき、必要に応じてファイル出力する」

 

ようにします。この際の問題は「文字列ポインターの指すデータ領域が文字列の追加でオーバーフローしないような管理」であり、

 

(1)まずオーバーフローしないような十分なサイズの文字列領域を確保する。→コンピューターリソース効率が悪いです。(ファイル出力ベースのものはこっちの発想ですね。)

(2)文字列の長さを管理して、追加する文字列の長さに応じた新しい文字列領域を確保し、旧い文字列をコピー、追加文字列を付加して、都度文字列データを更新し、旧いデータは廃棄する。→処理速度面で劣りますが、無駄なメモリーは使いません。多くの文字列処理クラスでこれを使っていると思います。

 

という選択肢があると思うので、後者を取ることにします。では、ResourceList04.cppを解説してゆきます。(文字列データ化で重要なところは水色にしておきます。)

 

【ResourceList04.cpp】

//////////////////////////////////////////////////////////////////////////
//https://learn.microsoft.com/ja-jp/windows/win32/menurc/using-resources
//次の例では、Hand.exe ファイル内のすべてのリソースの一覧を作成します。
//リストは、Resinfo.txt ファイルに書き込まれます。
//////////////////////////////////////////////////////////////////////////

#include    <windows.h>
#include    <strsafe.h>
#include    <stdio.h>
#include    <conio.h>    //getch()使用の為

#define     UNICODE    0    //ANSI(TCHAR == char)

///////////////////////////////
// CRESLISTクラス定義ファイル
// Copyright (c) August, 2024
//        By Ysama
///////////////////////////////

class CRESLIST
{
private:
    HMODULE m_hExe;                    //EXEファイルのハンドル
    TCHAR m_ResFileName[MAX_PATH] = {'\0'};    //リソースファイル名(拡張子前FileName + "_resinfo.txt")
    //解説:ファイルベースを止めるので、"HANDLE m_hFile;"は不要となる一方、文字列管理の為のポインターと文字列長管理の為の整数変数を追加します。尚、リソース情報ファイル名はNULL初期化しています。(↑)
    TCHAR* m_Data;            //リソースリストのデータポインター ー解説:ここにリソース情報文字列を作ります。
    size_t m_DataLen;        //m_Dataの長さ
public:
    CRESLIST();                        //コンストラクター1
    CRESLIST(LPCTSTR);                //コンストラクター2
    ~CRESLIST();                    //デストラクター
    //解説:メンバー関数も一新します。詳細は追加、変更部分を水色にして各関数で説明します。

    void Init();                    //初期化処理
    bool BeginResList(LPCTSTR);        //実行可能ファイル名を与えて開始
    LPCTSTR GetData() {return m_Data;}    //m_Dataを返す
    bool AddData(LPCTSTR, size_t);    //m_Dataに長さsize_tの文字列を追加する
    bool SaveList();                //現在開いているファイルのリソースリストを保存する
    static bool EnumTypesFunc(HMODULE, LPCTSTR, LONG);                    //リソース種類取得用コールバック関数
    static bool EnumNamesFunc(HMODULE, LPCTSTR, LPCTSTR, LONG);            //リソース名取得用コールバック関数
    static bool EnumLangsFunc(HMODULE, LPCTSTR, LPCTSTR, WORD, LONG);    //リソース言語取得用コールバック関数
};

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

    Init();        //初期化処理
}

//コンストラクター2    //解説:最初から実行可能ファイルが特定されている場合はこれを使い、直ぐに処理を行います。
CRESLIST::CRESLIST(LPCTSTR FileName) {

    Init();                    //初期化処理
    BeginResList(FileName);    //実行可能ファイル名を与えて開始
}

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

    //「念の為」処理-   解説:何故「念の為」なのかはInitを参照してください。
    Init();
}

//初期化処理
void CRESLIST::Init() {

    //解説:リソース情報ファイル名はNULL初期化されているので、NULLでなければ既に処理を行ったことを意味します。
    if(*m_ResFileName) {    //解説:これによりInitを呼べばいつでも処理を開始できます。
        //ロードした実行可能ファイルをアンロード
        FreeLibrary(m_hExe);
        //リソースリストのデータを開放
        delete [] m_Data;
        //リソース情報ファイルパス名を初期化(初期状態)
        m_ResFileName[0] = TCHAR('\0');
    }
    m_hExe = NULL;
    m_Data = new TCHAR[1] {'\0'}; 
  //解説:NULLだけの文字列(1文字)
    m_DataLen = 1;
}

//実行可能ファイル名を与えて開始
bool CRESLIST::BeginResList(LPCTSTR FileName) {

    TCHAR szBuffer[MAX_PATH];        //info情報用バッファ
    size_t cbString;                //szBufferの文字列長
    HRESULT hResult;                //結果判定用変数

    //既に実行可能ファイルをロードしていれば一旦終了させる
    if(*m_ResFileName) {
        Init();
    }

    //szBuffer名の設定
    lstrcpyn(TEXT(m_ResFileName), TEXT(FileName), lstrlen(TEXT(FileName)) - 3);    //".exe"は4文字だが、lstrcpynはNULLを含むので(-4 + 1)となる
    lstrcat(TEXT(m_ResFileName), TEXT("_resinfo.txt"));
    //リソースを列挙する実行可能ファイルをロードする
    m_hExe = LoadLibrary(TEXT(FileName));
    if(m_hExe == NULL) {
        printf("実行可能ファイルを読み込めません。");
        getch();    //コンソールを閉じない為
        return FALSE;
    }
    //リストファイルデータの作成開始
    hResult = StringCchPrintf(szBuffer, MAX_PATH,
        TEXT("%s\r\nのファイルには以下のリソースがあります。\r\n"), FileName);
    if(FAILED(hResult)) {
        printf("実行可能ファイルのリソースリストを作成できません。");
        getch();    //コンソールを閉じない為
        return FALSE;
    }
    hResult = StringCchLength(szBuffer, MAX_PATH, &cbString);
    if(FAILED(hResult)) {
        printf("文字列が指定した長さを超え、実行可能ファイルのリソースリストを作成できません。");
        getch();    //コンソールを閉じない為
        return FALSE;
    }
    //m_Dataの更新-解説:以下各処理で書き込みでデータ(szBuffer)を長さ(cbString)と共にAddDataへ送ります。
    AddData(szBuffer, cbString);
    //リソース種類の列挙を行う
    EnumResourceTypes(m_hExe,                //読み込みモジュールのハンドル
        (ENUMRESTYPEPROC)EnumTypesFunc,        //コールバックファンクションアドレス
        //解説:静的関数にこのオブジェクト(クラスインスタンス)のアドレス(this)を"lParam"として送ります。(詳細下記)

        (LONG)this);                        //補足パラメーター
    return TRUE;
}

//m_Dataに文字列を追加する
bool CRESLIST::AddData(LPCTSTR text, size_t length) {
 

    bool flag = FALSE;
    
//m_Dataの更新
    m_DataLen += length;                //mDataLen(NULL織り込み済)にtextの長さlengthを追加
    TCHAR* str = new TCHAR[m_DataLen];    //暫定文字列
    HRESULT res = StringCchCopy(str, m_DataLen, m_Data);    //旧m_Dataをコピー
    if(res == S_OK) {
        res = StringCchCat(str, m_DataLen, text);    
//textを追加
        if(res == S_OK) {
            delete [] m_Data;                        
//旧m_Dataを解放
            m_Data = new TCHAR[m_DataLen];            //再初期化
            StringCchCopy(m_Data, m_DataLen, str);    //m_Dataにstrをコピー(手抜きでエラー処理なし)
            flag = TRUE;    //解説:正常に処理された場合のみflaghaTRUEになります。
        }
        else {
            if(res == STRSAFE_E_INSUFFICIENT_BUFFER)
                printf("Error: %s\r\n", "STRSAFE_E_INSUFFICIENT_BUFFER");
            else if(res == STRSAFE_E_INVALID_PARAMETER)
                printf("Error: %s\r\n", "STRSAFE_E_INVALID_PARAMETER");
            m_DataLen -= length;
        }
    }
    else
        m_DataLen -= length;   
//解説:エラーであればm_DataLenを元に戻します。
    delete [] str;                        //暫定文字列strを解放
    return flag;
 

//解説:ここが不具合に気づいたところです。最初はStringCchCopy/Cat/Lengthが原因ではないかと考えました。
/* 以下は正常に動く。
    //m_Dataの更新
    m_DataLen += length;                    //mDataLen(NULL織り込み済)にtextの長さlengthを追加
    TCHAR* str = new TCHAR[m_DataLen];        //暫定文字列
    lstrcpy(str, m_Data);    //旧m_Dataをコピー
    lstrcat(str, text);        //textを追加
    delete [] m_Data;                        //旧m_Dataを解放
    m_Data = new TCHAR[m_DataLen];            //再初期化(+1は終端(NULL)用)
    lstrcpy(m_Data, str);    //m_Dataにstrをコピー
    delete [] str;                            //暫定文字列strを解放

 

//解説:しかし、結局はオリジナルコードにあったsizeofを使った文字列長取得に問題があったことが判りました。
  文字列の長さを求めようとして
sizeofを使うと「ポインターの長さ」が返ってきます。
  printf("
sizeof(str): %d sizeof(TCHAR):%d\r\n", sizeof(str), sizeof(TCHAR));
  printf("
m_DataLen: %d sizeof(str) / sizeof(TCHAR): %d)\r\n", m_DataLen, sizeof(str) / sizeof(TCHAR));
  (出力)
  sizeof(str):
4 sizeof(TCHAR):1
  m_DataLen: 133 sizeof(str) / sizeof(TCHAR): 4)
//解説:従って以下ではm_DataLenを使っています。

*/

}

//現在開いているファイルのリソースリストを保存する
bool CRESLIST::SaveList() {

    HANDLE hFile;                                    
//リソース情報ファイルハンドル
    hFile = CreateFile(TEXT(TEXT(m_ResFileName)),    //リソースファイル名
        GENERIC_READ | GENERIC_WRITE,                //アクセスモード
        0,                                            //シェアモード
        (LPSECURITY_ATTRIBUTES) NULL,                //デフォルトセキュリティ
        CREATE_ALWAYS,                                //フラグの作成
        FILE_ATTRIBUTE_NORMAL,                        //ファイルアトリビュート
        (HANDLE) NULL);                                //テンプレート無し
    if(hFile == INVALID_HANDLE_VALUE) {
        printf(TEXT("リソース情報ファイルを作成できませんでした。"));
        getch();    //コンソールを閉じない為
        return FALSE;
    }
    DWORD cbWritten;                    
//resource infoファイルへの書き込みサイズ
    WriteFile(hFile,                    //resource infoを書き込むファイルハンドル
        m_Data,                            //書き込みデーター解説:一挙にm_Dataを書き込みます。
        (DWORD)m_DataLen,                //書き込みサイズ
        &cbWritten,                        //書き込みバイト数
        NULL);                            //オーバーラップドI/Oは無し
    CloseHandle(hFile);
    printf(m_Data);
    printf("リソース情報ファイルを書き出しました。");
    return TRUE;
}

 

//解説:以下の3つのコールバック関数に共通する問題ですが、グローバル空間に置かれた静的関数にとって、スタック領域に作られたオブジェクト(クラスインスタンス)のアドレスが分からないので、補足パラメーター(lParam)にそのアドレス(this)を入れて呼び、コールバック関数ではそのクラスインスタンスの関数であるAddDataを呼びます
//リソース種類取得用コールバック関数EnumTypesFunc(HMODULE, LPCTSTR, LONG)
bool CRESLIST::EnumTypesFunc(HMODULE hModule, LPCTSTR lpType, LONG lParam) {

    TCHAR szBuffer[MAX_PATH];        //infoファイル用プリントバッファ
    size_t cbString;                //szBufferの文字列長
    HRESULT hResult;                //結果判定用変数

    //リソース情報ファイルにリソース種類を書き込む
    //種類は文字列または符号なし整数であり、事前にテストを行う

    if(!IS_INTRESOURCE(lpType)) {
        hResult = StringCchPrintf(szBuffer, MAX_PATH, TEXT("種類:%s\r\n"), lpType);
        if (FAILED(hResult)) {
            //(読み込み失敗時の処理を記述)
            return FALSE;
        }
    }
    else
    {
        hResult = StringCchPrintf(szBuffer, MAX_PATH, TEXT("種類:%u\r\n"), (USHORT)lpType);
        if (FAILED(hResult)) {
            //(読み込み失敗時の処理を記述)
            return FALSE;
        }
    }
    hResult = StringCchLength(szBuffer, MAX_PATH, &cbString);
    if(FAILED(hResult)) {
        //(読み込み失敗時の処理を記述)
        return FALSE;
    }
    //m_Dataの更新
    ((CRESLIST*)lParam)->AddData(szBuffer, cbString);    //解説:lParamthis(クラスインスタンス)のアドレス
    //同種のリソースを総て検索する
    EnumResourceNames(hModule, lpType, (ENUMRESNAMEPROC)EnumNamesFunc, lParam);
    return TRUE;
}

//リソース名取得用コールバック関数EnumNamesFunc(HMODULE, LPCSTR, LPCSTR, LONG)
bool CRESLIST::EnumNamesFunc(HMODULE hModule, LPCTSTR lpType, LPCTSTR lpName, LONG lParam) {

    TCHAR szBuffer[MAX_PATH];        //infoファイル用プリントバッファ
    size_t cbString;                //szBufferの文字列長
    HRESULT hResult;                //結果判定用変数

    //リソース情報ファイルにリソース名を書き込む
    //リソース名は文字列または符号なし整数であり、事前にテストを行う

    if(!IS_INTRESOURCE(lpName)) {
        hResult = StringCchPrintf(szBuffer, MAX_PATH, TEXT("\tリソース名:%s\r\n"), lpName);
        if(FAILED(hResult)) {
            //(読み込み失敗時の処理を記述)
            return FALSE;
        }
    }
    else
    {
        hResult = StringCchPrintf(szBuffer, MAX_PATH, TEXT("\tリソース名:%s\r\n"), (USHORT)lpName);
        if(FAILED(hResult)) {
            //(読み込み失敗時の処理を記述)
        }
    }
    hResult = StringCchLength(szBuffer, MAX_PATH, &cbString);
    if(FAILED(hResult)) {
            //(読み込み失敗時の処理を記述)
        return FALSE;
    }
    //m_Dataの更新
    ((CRESLIST*)lParam)->AddData(szBuffer, cbString);    //解説:lParamthis(クラスインスタンス)のアドレス
    //同種のリソースの言語を総て検索する
    EnumResourceLanguages(hModule, lpType, lpName, (ENUMRESLANGPROC)EnumLangsFunc, lParam);
    return TRUE;
}

//リソース言語取得用コールバック関数EnumLangsFunc(HMODULE, LPCTSTR, LPCTSTR, WORD, LONG)
bool CRESLIST::EnumLangsFunc(HMODULE hModule, LPCTSTR lpType, LPCTSTR lpName, WORD wLang, LONG lParam) {

    HRSRC hResInfo;        //リソース情報ハンドル
    TCHAR szBuffer[MAX_PATH];        //infoファイル用プリントバッファ
    size_t cbString;                //szBufferの文字列長
    HRESULT hResult;                //結果判定用変数

    hResInfo = FindResourceEx(hModule, lpType, lpName, wLang);
    //リソース情報ファイルへリソース情報を書き込む
    hResult = StringCchPrintf(szBuffer, MAX_PATH, TEXT("\t\tリソース言語: %u\r\n"), (USHORT) wLang);
    if(FAILED(hResult)) {
        printf("リソース言語を読み込めません。");
        return FALSE;
    }
    hResult = StringCchLength(szBuffer, MAX_PATH, &cbString);
    if(FAILED(hResult)) {
        printf("リソース言語の長さを読み込めません。");
        return FALSE;
    }
    //m_Dataの更新
    ((CRESLIST*)lParam)->AddData(szBuffer, cbString);    //解説:lParamthis(クラスインスタンス)のアドレス
    //リソースハンドルとサイズをバッファーに書き込む
    hResult = StringCchPrintf(szBuffer, MAX_PATH, TEXT("\t\tリソースハンドル:%lx、サイズ:%lu\r\n\r\n"), hResInfo, SizeofResource(hModule, hResInfo));
    if(FAILED(hResult)) {
        //(読み込み失敗時の処理を記述)
        return FALSE;
    }
    hResult = StringCchLength(szBuffer, MAX_PATH, &cbString);
    if (FAILED(hResult))
    {
        //(読み込み失敗時の処理を記述)
        return FALSE;
    }
    //m_Dataの更新
    ((CRESLIST*)lParam)->AddData(szBuffer, cbString);    //解説:lParamthis(クラスインスタンス)のアドレス
    return TRUE;
}

/////////////////////////
// Entry Point Function
//外部変数
/////////////////////////

TCHAR g_FileName[MAX_PATH];        //リソースを取得するEXEファイル名

//main関数(エントリーポイント)
int main() {

    //対象ファイル名の指定
    //lstrcpy(g_FileName, TEXT("C:\\Users\\ysama\\Programing\\Borland C++\\TextToSpeech\\Debug\\TextToSpeech.exe"));

    lstrcpy(g_FileName, TEXT("C:\\Users\\ysama\\Programing\\C++ Programing\\Microsoft\\ResourceList\\Debug\\ResourceList04.exe"));
    CRESLIST rl(g_FileName);
    rl.SaveList();
    getch();                    //コンソールを閉じない為
    return 0;
}

 

いかがだったでしょうか?外部記憶装置のファイルに落とすよりも、メモリーに文字列データとして落とす方が、(後で色々と使えるので)すっきりしませんか?また、↑で見た問題(静的関数とクラスメンバー関数のアドレス渡し等)エラーが出ることにより、「え"~」と面倒に感じることもあると思います。一方、「どうしてそうなるの?」という疑問が生じ、C++がどのようにコードをメモリーに配置しているかが分かるようになるので、

 

この謎解きが楽しいんですよね、プログラミングって。

 

次回は最後のブラッシュアップで、

「種類:(数字)」や「リソース言語:(数字)」の表示が格好悪いので、これを改善します。そして、その後に、

 

BCCSkeltonを使ったウィンドウプログラムにします。乞ご期待。

 

さてさて、Microsoft Learnのオリジナル抜粋コード取り敢えず動かし、更に前回は処理関数を独立させて構造化させました。そして、...

 

今回はもっと難物のクラス化

 

に挑戦します。では、

 

クラスとは何か?

 

これは色々と書かれていますが、以外にもMicrosoft Learnでは抽象的な概念等の説明は乏しくここに少し述べられているだけのようです。)要すれば、

 

(1)「"オブジェクト" は、1 つの単位として扱うことができるコードとデータの組み合わせです。」

(2)「各オブジェクトは、"クラス" によって定義されます。」

(3)「オブジェクトはアプリケーションの要素であり、クラスの "インスタンス" を表しています。 フィールド、プロパティ、メソッド、およびイベントは、オブジェクトの構成要素であり、オブジェクトのメンバーを構成します。」

 

ということですが、分かりましたか?

 

↑の話をC++プログラミングで言い換えるならば、

 

(1)特定の仕事をするコードとデータの組み合わせの単位であるプログラムの部品をオブジェクトといいます。

(2)オブジェクトという実体は、その設計図である"クラス"定義によって定められ、"インスタンス"として生成されます。

(3)オブジェクトの構成要素には、各種フィールド(「メンバー変数」)、(オブジェクトの重要な属性に関わる変数へのアクセスが規定された)プロパティ、フィールドやプロパティを処理するメソッド(「関数」)、およびメソッドのトリガーとなるイベント(割込み)があります。

 

更にこれを本件ResourceListでいうならば、

 

(1)「実行可能ファイルのリソース情報を取得、出力する」という仕事を行うコードとデータをまとめる。(前回のResourceLIst02.cpp参照)

(2)そのオブジェクトの設計図としてCRESLISTクラスを定義し、"インスタンス"を生成する。

(3)CRESLISTクラスのメンバー変数、メンバー関数を特定してクラス宣言に盛り込む。

 

ことによりクラス化が実現します。クラス化する際に必要な変数と関数は前々回で示したコールバック関数3つと関連変数、前回示したResourceList関数と関連変数になりますので、それらを纏め、C++のクラスとして定義します。

 

【ResourceList03.cppのエントリーポイント関数】

/////////////////////////////////////////////////////////////////////////
//https://learn.microsoft.com/ja-jp/windows/win32/menurc/using-resources
//次の例では、Hand.exe ファイル内のすべてのリソースの一覧を作成します。
//リストは、Resinfo.txt ファイルに書き込まれます。
//////////////////////////////////////////////////////////////////////////

#include    <windows.h>
#include    <strsafe.h>
#include    <stdio.h>
#include    <conio.h>    //getch()使用の為

#define     UNICODE    0    //ANSI(TCHAR == char)
 

//外部変数
TCHAR g_FileName[MAX_PATH];        //リソースを取得するEXEファイル名

//main関数(エントリーポイント)
int main() {

    //対象ファイル名の指定
    //lstrcpy(g_FileName, TEXT("C:\\Users\\ysama\\Programing\\Borland C++\\TextToSpeech\\Debug\\TextToSpeech.exe"));
    lstrcpy(g_FileName, TEXT("C:\\Users\\ysama\\Programing\\C++ Programing\\Microsoft\\ResourceList\\Debug\\ResourceList03.exe"));
    CRESLIST rl;    //解説クラスインスタンス(オブジェクト)を生成する
    rl.InitResList(g_FileName);    //解説クラスインスタンスのInitResListメソッドを呼ぶ
    rl.QuitResList();    //解説クラスインスタンスのQuitResListメソッドを呼ぶ
    getch();                    //コンソールを閉じない為
    return 0;
}

 

どうでしょう?とてもすっきりしたプログラムになったのではないでしょうか?変数宣言の様に見えるCRESLIST(最初の'C'はClassを表すようです。)がクラスインスタンスの宣言です。変数のようですが、InitResListやQuitResListのような内蔵するメンバー関数を'.'(ポオインターの場合は->)で呼ぶことが出来ます。

 

【ResourceList03.cppのクラス宣言】

//解説:注意:以下は#include、#defineプリプロセッサーの後、且つ↑のエントリーポイント関数の前に配置することが必要です。)

///////////////////////////////
// CRESLISTクラス定義ファイル
// Copyright (c) August, 2024
//        By Ysama
///////////////////////////////

class CRESLIST
{
private:     //解説:アクセス制限子です。privateはクラス内部使用だけに制限されます。
    HMODULE m_hExe;                    //EXEファイルのハンドル
    HANDLE m_hFile;                    //リソースファイルハンドル
    TCHAR m_ResFileName[MAX_PATH];    //リソースファイル名(拡張子前FileName + "_resinfo.txt")

public:     //解説:アクセス制限子です。publicは外部からもアクセスできます。
    CRESLIST();                        //コンストラクター
    ~CRESLIST();                    //デストラクター
    bool InitResList(LPCTSTR);        //実行可能ファイル名を与えて開始
    void QuitResList();                //いったん終了する際に必ず呼ぶ
    static bool EnumTypesFunc(HMODULE, LPCTSTR, LONG);                    //リソース種類取得用コールバック関数
    static bool EnumNamesFunc(HMODULE, LPCTSTR, LPCTSTR, LONG);            //リソース名取得用コールバック関数
    static bool EnumLangsFunc(HMODULE, LPCTSTR, LPCTSTR, WORD, LONG);    //リソース言語取得用コールバック関数
//解説:コンストラクター、デストラクターを含むメンバー関数は外部利用が出来るようにpublicでプロトタイプを宣言します。(ここでメンバー関数の実装を行ってもよいのですが、長くなると可読性が下がるので、故意に別途実装するようにしています。)

//解説:また、コールバック関数は、そのアドレスがクラス宣言の中で他のメンバー関数で呼ばれますので、コンパイル段階でアドレスが決定していないとコンパイルエラーになります。その為にクラスで変数や関数のアドレスが求められる場合、「クラス共通の静的変数、関数」として宣言し、コンパイル時にアドレスを固定することが必要となります。(注)

//注:一般にC++のクラスオブジェクトはスタックに生成されるため、コンパイル時には相対アドレス("this->"によりオブジェクト先頭からの位置が決まるようです)となり、プログラムがロードされた段階で絶対アドレスが決定します。従って、クラスメンバー関数や変数のアドレスを使う場合には、一旦クラスオブジェクトが生成された後、その絶対アドレスをダミーの静的関数を介在させてそのクラスオブジェクトに渡して利用する必要があります。(関心がある方はECCSkeltonのCSDI.hヘッダーにあるDummyProc(ダミーのコールバック静的関数)の処理を参照して下さい。)

};    //解説:C++のクラス定義ではclass (クラス名 : 承継元){}の後に';'をつけるのを忘れないでください。

//コンストラクター
CRESLIST::CRESLIST() {    //解説:クラス定義でプロトタイプ宣言を行った関数を実装する場合、「クラスの領土内のみ通用」という意味の"CRESLIST::"を関数名の前に付けます。
    //初期化処理
    m_hExe = NULL;
    m_hFile = NULL;
    m_ResFileName[0] = TCHAR('\0');
}

//デストラクター
CRESLIST::~CRESLIST() {
    //「念の為」処理
    QuitResList();
}

//実行可能ファイル名を与えて開始
bool CRESLIST::InitResList(LPCTSTR FileName) {    //解説:前回のResourceList関数に相当します。

    TCHAR szBuffer[MAX_PATH];        //infoファイル用プリントバッファ
    DWORD cbWritten;                //resource infoファイルへの書き込みサイズ
    size_t cbString;                //szBufferの文字列長
    HRESULT hResult;                //結果判定用変数

    //リソースを列挙するEXEファイルをロードする
    m_hExe = LoadLibrary(TEXT(FileName));
    if(m_hExe == NULL) {
        printf("実行可能ファイルを読み込めません。");
        getch();    //コンソールを閉じない為
        return FALSE;
    }
    //リソース情報を収容するファイルを開く
    lstrcpyn(TEXT(m_ResFileName), TEXT(FileName), lstrlen(TEXT(FileName)) - 3);    //".exe"は4文字だが、lstrcpynはNULLを含むので(-4 + 1)となる
    lstrcat(TEXT(m_ResFileName), TEXT("_resinfo.txt"));
    m_hFile = CreateFile(TEXT(TEXT(m_ResFileName)),    //リソースファイル名
        GENERIC_READ | GENERIC_WRITE,                //アクセスモード
        0,                                            //シェアモード
        (LPSECURITY_ATTRIBUTES) NULL,                //デフォルトセキュリティ
        CREATE_ALWAYS,                                //フラグの作成
        FILE_ATTRIBUTE_NORMAL,                        //ファイルアトリビュート
        (HANDLE) NULL);                                //テンプレート無し
    if(m_hFile == INVALID_HANDLE_VALUE) {
        printf(TEXT("リソース情報ファイルを作成できませんでした。"));
        getch();    //コンソールを閉じない為
        return FALSE;
    }
    //ロードされたファイルのすべてのリソースを読み込む
    hResult = StringCchPrintf(szBuffer, sizeof(szBuffer) / sizeof(TCHAR),
        TEXT("%s\r\nのファイルには以下のリソースがあります。\r\n"), FileName);
    if(FAILED(hResult)) {
        printf("実行可能ファイルのリソースを読み込めません。");
        getch();    //コンソールを閉じない為
        return FALSE;
    }
    hResult = StringCchLength(szBuffer, sizeof(szBuffer) / sizeof(TCHAR), &cbString);
    if(FAILED(hResult)) {
        //(読み込み失敗時の処理を記述)
        printf("文字列が指定した長さを超えました。");
        getch();    //コンソールを閉じない為
        return FALSE;
    }
    WriteFile(m_hFile,                    //resource infoを書き込むファイルハンドル
        szBuffer,                        //書き込みバッファー
        (DWORD)cbString,                //szBufferの書き込みサイズ
        &cbWritten,                        //書き込みバイト数
        NULL);                            //オーバーラップドI/Oは無し
    EnumResourceTypes(m_hExe,            //読み込みモジュールのハンドル
        (ENUMRESTYPEPROC)EnumTypesFunc,    //コールバックファンクションアドレス
        (LONG)m_hFile);                    //補足パラメーター
    return TRUE;
}

void CRESLIST::QuitResList() {    //いったん終了する際に必ず呼ぶ(解説:複数回の利用を考え、InitResListから分離しました。)

    //ロードして、リソースを列挙したた実行可能ファイルをアンロードし、リソース情報ファイルを閉じる。
    FreeLibrary(m_hExe);
    CloseHandle(m_hFile);
    printf("リソース情報ファイルを書き出しました。");
}

//解説:以下の3つの静的コールバック関数は(問題コードを含め)そのままです。
//リソース種類取得用コールバック関数EnumTypesFunc(HMODULE, LPCTSTR, LONG)
bool CRESLIST::EnumTypesFunc(HMODULE hModule, LPCTSTR lpType, LONG lParam) {

    TCHAR szBuffer[MAX_PATH];        //infoファイル用プリントバッファ
    DWORD cbWritten;                //resource infoファイルへの書き込みサイズ
    size_t cbString;                //szBufferの文字列長
    HRESULT hResult;                //結果判定用変数

    //リソース情報ファイルにリソース種類を書き込む
    //種類は文字列または符号なし整数であり、事前にテストを行う

    if(!IS_INTRESOURCE(lpType)) {
        hResult = StringCchPrintf(szBuffer, sizeof(szBuffer) / sizeof(TCHAR), TEXT("種類:%s\r\n"), lpType);
        if (FAILED(hResult)) {
            //(読み込み失敗時の処理を記述)
            return FALSE;
        }
    }
    else
    {
        hResult = StringCchPrintf(szBuffer, sizeof(szBuffer) / sizeof(TCHAR), TEXT("種類:%u\r\n"), (USHORT)lpType);
        if (FAILED(hResult)) {
            //(読み込み失敗時の処理を記述)
            return FALSE;
        }
    }
    hResult = StringCchLength(szBuffer, sizeof(szBuffer)/sizeof(TCHAR), &cbString);
    if(FAILED(hResult)) {
        //(読み込み失敗時の処理を記述)
        return FALSE;
    }
    WriteFile((HANDLE)lParam, szBuffer, (DWORD)cbString, &cbWritten, NULL);
    //同種のリソースを総て検索する
    EnumResourceNames(hModule, lpType, (ENUMRESNAMEPROC)EnumNamesFunc, lParam);
    return TRUE;
}

//リソース名取得用コールバック関数EnumNamesFunc(HMODULE, LPCSTR, LPCSTR, LONG)
bool CRESLIST::EnumNamesFunc(HMODULE hModule, LPCTSTR lpType, LPCTSTR lpName, LONG lParam) {

    TCHAR szBuffer[MAX_PATH];        //infoファイル用プリントバッファ
    DWORD cbWritten;                //resource infoファイルへの書き込みサイズ
    size_t cbString;                //szBufferの文字列長
    HRESULT hResult;                //結果判定用変数

    //リソース情報ファイルにリソース名を書き込む
    //リソース名は文字列または符号なし整数であり、事前にテストを行う

    if(!IS_INTRESOURCE(lpName)) {
        hResult = StringCchPrintf(szBuffer, sizeof(szBuffer) / sizeof(TCHAR), TEXT("\tリソース名:%s\r\n"), lpName);
        if(FAILED(hResult)) {
            //(読み込み失敗時の処理を記述)
            return FALSE;
        }
    }
    else
    {
        hResult = StringCchPrintf(szBuffer, sizeof(szBuffer) / sizeof(TCHAR), TEXT("\tリソース名:%s\r\n"), (USHORT)lpName);
        if(FAILED(hResult)) {
            //(読み込み失敗時の処理を記述)
        }
    }
    hResult = StringCchLength(szBuffer, sizeof(szBuffer) / sizeof(TCHAR), &cbString);
    if(FAILED(hResult)) {
            //(読み込み失敗時の処理を記述)
        return FALSE;
    }
    WriteFile((HANDLE)lParam, szBuffer, (DWORD)cbString, &cbWritten, NULL);
    //同種のリソースの言語を総て検索する
    EnumResourceLanguages(hModule, lpType, lpName, (ENUMRESLANGPROC)EnumLangsFunc, lParam);
    return TRUE;
}

//リソース言語取得用コールバック関数EnumLangsFunc(HMODULE, LPCTSTR, LPCTSTR, WORD, LONG)
bool CRESLIST::EnumLangsFunc(HMODULE hModule, LPCTSTR lpType, LPCTSTR lpName, WORD wLang, LONG lParam) {

    HRSRC hResInfo;        //リソース情報ハンドル
    TCHAR szBuffer[MAX_PATH];        //infoファイル用プリントバッファ
    DWORD cbWritten;                //resource infoファイルへの書き込みサイズ
    size_t cbString;                //szBufferの文字列長
    HRESULT hResult;                //結果判定用変数

    hResInfo = FindResourceEx(hModule, lpType, lpName, wLang);
    //リソース情報ファイルへリソース情報を書き込む
    hResult = StringCchPrintf(szBuffer, sizeof(szBuffer) / sizeof(TCHAR), TEXT("\t\tリソース言語: %u\r\n"), (USHORT) wLang);
    if(FAILED(hResult)) {
        printf("リソース言語を読み込めません。");
        return FALSE;
    }
    hResult = StringCchLength(szBuffer, sizeof(szBuffer) / sizeof(TCHAR), &cbString);
    if(FAILED(hResult)) {
        printf("リソース言語の長さを読み込めません。");
        return FALSE;
    }
    WriteFile((HANDLE)lParam, szBuffer, (DWORD)cbString, &cbWritten, NULL); 
    //リソースハンドルとサイズをバッファーに書き込む
    hResult = StringCchPrintf(szBuffer, sizeof(szBuffer) / sizeof(TCHAR), TEXT("\t\tリソースハンドル:%lx、サイズ:%lu\r\n\r\n"), hResInfo, SizeofResource(hModule, hResInfo));
    if(FAILED(hResult)) {
        //(読み込み失敗時の処理を記述)
        return FALSE;
    }
    hResult = StringCchLength(szBuffer, sizeof(szBuffer) / sizeof(TCHAR), &cbString);
    if (FAILED(hResult))
    {
        //(読み込み失敗時の処理を記述)
        return FALSE;
    }
    WriteFile((HANDLE)lParam, szBuffer, (DWORD)cbString, &cbWritten, NULL);
    return TRUE;
}

 

いかがでしたか?このCRESLISTクラスの定義を独立ファイル(CRESLIST.h)として#includeで読み込めば、エントリーポイント関数本体の記述がとても簡単になりますね。

 

でも、

 

「出力用テキストファイルを開いて、そこにコールバック関数で取得した値を順次書き込んでいき、最後に終了処理をする」という処理がドン臭く感じます。次回はここで書いた、

 

(4)クラスオブジェクトしたものを、よりポータブルにすべく「ファイル出力」を「文字列データ」に変更します。(ファイル出力ももちろん可能です。)

 

という処理に変更しようと思います。では、では。

 

 

前回取り敢えず動く形にしましたが、リソース情報リスト処理を(将来ウィンドウズプログラムに移すので)それをエントリーポイント関数(現在はコンソールプログラムなのでmain()関数)から切り離し、ResourceListという関数外出ししてみます。

以下は前回のResourceList01.cppからResourceList関数を外出ししたResourceList02.cppの主要変更点を抜粋したものです。

 

【ResourceList02.cpp】(抜粋)

//関数プロトタイプ宣言
bool ResourceList(LPCTSTR, HANDLE*);    //解説:コールバック関数の宣言は止め、外出し関数のみとした。

//外部変数
TCHAR g_FileName[MAX_PATH];        //リソースを取得するEXEファイル名
HANDLE g_hFile;                    //リソースファイルのグローバルハンドル

/* 解説:ローカル変数化-解説:前回外部変数であった以下の変数はResouceList関数のローカル関数に移行しました。

TCHAR g_ResFileName[MAX_PATH];    //リソースファイル名(拡張子前g_FileName + "_resinfo.txt")
HMODULE g_hExe;                    //EXEファイルのハンドル
TCHAR szBuffer[MAX_PATH];        //infoファイル用プリントバッファ
DWORD cbWritten;                //resource infoファイルへの書き込みサイズ
size_t cbString;                //szBufferの文字列長
HRESULT hResult;
*/


//解説:処理を外出ししたのでmain関数はさっぱりしました。
//main関数(エントリーポイント)
int main() {

    //対象ファイル名の指定
    //lstrcpy(g_FileName, TEXT("C:\\Users\\(省略)\\TextToSpeech\\Debug\\TextToSpeech.exe"));
    lstrcpy(g_FileName, TEXT("C:\\Users\\(省略)\\ResourceList\\Debug\\ResourceList02.exe"));
    ResourceList(g_FileName, &g_hFile);
    getch();                    //コンソールを閉じない為
    return 0;
}

/*コールバック関数の順を呼び出し逆順とする
解説:CやC++では、自分より後に宣言される関数は予めプロトタイプを宣言することが必要です。前回はコールバック変数すべてを宣言していましたが、EnumTypesFuncを呼び出す
ResourceList関数が最後尾に移動したので、その上にEnumTypesFunc、それがEnumNamesFuncを予備すので、その上にEnumNamesFunc、それがEnumLangsFuncを呼び出すので、EnumLangsFuncは更にその上に配置しました。
 

//リソース言語取得用コールバック関数EnumLangsFunc(HANDLE, LPSTR, LPSTR, WORD, LONG)
BOOL EnumLangsFunc(HMODULE hModule, LPCTSTR lpType, LPCTSTR lpName, WORD wLang, LONG lParam)
  ↑
//リソース名取得用コールバック関数
//EnumNamesFunc(HANDLE, LPSTR, LPSTR, LONG)
 BOOL EnumNamesFunc(HMODULE hModule, LPCTSTR lpType, LPTSTR lpName, LONG lParam)
  ↑

//リソース種類取得用コールバック関数
//EnumTypesFunc(HANDLE, LPSTR, LONG)
BOOL EnumTypesFunc(HMODULE hModule, LPTSTR lpType, LONG lParam)
  ↑

ResourceList(LPCTSTR, HANDLE*)

*/


//コンソール版リソースリスト関数
//ResourceList(LPCTSTR, HANDLE*)
//解説:最後に外出ししたResourceList関数が来ます。​

​​​​​​bool ResourceList(LPCTSTR FileName, HANDLE* ptFile) {
//解説:引数はロードする実行可能ファイル名とグローバル変数のファイルハンドルです。後者はグローバル変数で引数にする必要はないのですが、可読性を高め、呼び出し側のローカル変数でも対応できるようより汎用性を持たすべく(参照渡しに準じる)ポインター渡しにしています。
    HMODULE hExe;                    //EXEファイルのハンドル
    TCHAR ResFileName[MAX_PATH];    //リソースファイル名(拡張子前FileName + "_resinfo.txt")
    TCHAR szBuffer[MAX_PATH];        //infoファイル用プリントバッファ
    DWORD cbWritten;                //resource infoファイルへの書き込みサイズ
    size_t cbString;                //szBufferの文字列長
    HRESULT hResult;

    //リソースを列挙するEXEファイルをロードする
    hExe = LoadLibrary(TEXT(FileName));
    if(hExe == NULL) {
        printf("実行可能ファイルを読み込めません。");
        getch();    //コンソールを閉じない為
        return FALSE;
    }
    //リソース情報を収容するファイルを開く
    lstrcpyn(TEXT(ResFileName), TEXT(FileName), lstrlen(TEXT(FileName)) - 3);    //".exe"は4文字だが、lstrcpynはNULLを含むので(-4 + 1)となる
    lstrcat(TEXT(ResFileName), TEXT("_resinfo.txt"));
    *ptFile = CreateFile(TEXT(TEXT(ResFileName)),    //リソースファイル名
        GENERIC_READ | GENERIC_WRITE,                //アクセスモード
        0,                                            //シェアモード
        (LPSECURITY_ATTRIBUTES) NULL,                //デフォルトセキュリティ
        CREATE_ALWAYS,                                //フラグの作成
        FILE_ATTRIBUTE_NORMAL,                        //ファイルアトリビュート
        (HANDLE) NULL);                                //テンプレート無し
    if(*ptFile == INVALID_HANDLE_VALUE) {
        printf(TEXT("リソース情報ファイルを作成できませんでした。"));
        getch();    //コンソールを閉じない為
        return FALSE;
    }
    //ロードされたファイルのすべてのリソースを読み込む
    hResult = StringCchPrintf(szBuffer, sizeof(szBuffer) / sizeof(TCHAR),
        TEXT("%s\r\nのファイルには以下のリソースがあります。\r\n"), FileName);
    if(FAILED(hResult)) {
        //(読み込み失敗時の処理を記述)
        printf("実行可能ファイルのリソースを読み込めません。");
        getch();    //コンソールを閉じない為
        return FALSE;
    }
    hResult = StringCchLength(szBuffer, sizeof(szBuffer) / sizeof(TCHAR), &cbString);
    if(FAILED(hResult)) {
        //(読み込み失敗時の処理を記述)
        printf("文字列が指定した長さを超えました。");
        getch();    //コンソールを閉じない為
        return FALSE;
    }
    WriteFile(*ptFile,                    //resource infoを書き込むファイルハンドル
        szBuffer,                        //書き込みバッファー
        (DWORD)cbString,                //szBufferの書き込みサイズ
        &cbWritten,                        //書き込みバイト数
        NULL);                            //オーバーラップドI/Oは無し
    EnumResourceTypes(hExe,                //読み込みモジュールのハンドル
        (ENUMRESTYPEPROC)EnumTypesFunc,    //コールバックファンクションアドレス
        0);                                //補足パラメーター
    //ロードして、リソースを列挙したた実行可能ファイルをアンロードし、リソース情報ファイルを閉じる。
    FreeLibrary(hExe);
    CloseHandle(*ptFile);
    printf("リソース情報ファイルを書き出しました。");
    return TRUE;
}

 

 

ひょんなことで始めた前回に続き、今回はMicrosoft Learningの参考(抜粋)コードを補完して、取り敢えず動くようにしてみます。その際、コードはそのままで、コメントのみ英語を日本語に変えてゆきます。(

前回触れたように、このオリジナルコードには問題があります。しかし、発展の過程をそのまま示すために、バグフィクスして訂正する版まではそのままにしておきます。

 

オリジナル

英文コメント付きのオリジナルです。

 

【ResourceList01.cpp】

・やたら出てくるTCHARというのは、Microsoft発案の「蝙蝠コード」で、UNICODEが未定義だとcharに、定義するとwcharにマクロ変換してくれます。

・"RT_"という接頭語が出てきますが、これはWin_RTとは何ら関係なく、"Resource Type"を意味していると思われます。

・伝統的なprintf(sprintf)、strlen、strcpy等の関数は使わず、strsafe.hのStringCchPrintfStringCchLengthStringCchCopyを使っています。

・書き込みバッファーサイズ(NULLを含む書き込み可能文字数)を算出する際に"sizeof(szBuffer) / sizeof(TCHAR)"を使っています。しかし、結果的にはANSIベースでの値は"4 / 1"になってしまいます。

//////////////////////////////////////////////////////////////////////////
//https://learn.microsoft.com/ja-jp/windows/win32/menurc/using-resources
//次の例では、Hand.exe ファイル内のすべてのリソースの一覧を作成します。
//リストは、Resinfo.txt ファイルに書き込まれます。
//////////////////////////////////////////////////////////////////////////
/* --- Microsoft Learn ---
このコードでは、実行可能ファイルを読み込み、リソース情報を書き込むファイル
を作成し、EnumResourceTypes関数を呼び出して、モジュールで見つかった各リ
ソースの種類をアプリケーション定義のコールバック関数EnumTypesFuncに送信す
る方法を示します。 この型のコールバック関数については、「EnumResTypeProc」
を参照してください。 このコールバック関数は、EnumResourceNames関数を使用
して、指定した型内のすべてのリソースの名前を別のアプリケーション定義のコー
ルバック関数EnumNamesFuncに渡します。 この型のコールバック関数については、
「EnumResNameProc」を参照してください。
EnumNamesFuncとEnumResourceLanguages関数を使用して、指定した型と名前のす
べてのリソースの言語を3番目のコールバック関数EnumLangsFuncに渡します。
この型のコールバック関数については、「EnumResLangProc」を参照してください。
EnumLangsFuncは、指定した型、名前、言語のリソースに関する情報をResinfo.
txtファイルに書き込みます。
EnumResTypeProcのlpszTypeは、リソースIDまたは文字列へのポインター(リソ
ースID または型名を含む) であることに注意してください。EnumResNameProcと
EnumResLangProcのlpszTypeとlpszNameは似ています。列挙されたリソースを
読み込むには、適切な関数を呼び出すだけです。たとえば、メニューリソース
(RT_MENU) が列挙された場合は、lpszNameをLoadMenuに渡します。カスタム
リソースの場合は、lpszTypeとlpszNameをFindResource に渡します。
<参考:リソース種類について>
https://learn.microsoft.com/ja-jp/windows/win32/menurc/resource-types
*/

#include    <windows.h>
#include    <strsafe.h>   //解説:(私も知らなかったのですが)これはMicrosoftがC++の文字列操作関数のバッファーオーバーランリスクを軽減するために開発したライブラリーのようです。
#include    <stdio.h>
#include    <conio.h>    //getch()使用の為

#define     UNICODE    0    //ANSI(TCHAR == char)

//コールバック関数のプロトタイプ宣言
BOOL EnumTypesFunc(HMODULE hModule, LPTSTR lpType, LONG lParam);
BOOL EnumNamesFunc(HMODULE hModule, LPCTSTR lpType, LPTSTR lpName, LONG lParam);
BOOL EnumLangsFunc(HMODULE hModule, LPCTSTR lpType, LPCTSTR lpName, WORD wLang, LONG lParam);

//外部変数
TCHAR g_FileName[MAX_PATH];        //リソースを取得するEXEファイル名(解説:これは追加しました。)
TCHAR g_ResFileName[MAX_PATH];    //リソースファイル名(拡張子前g_FileName + "_resinfo.txt")(解説:同上)
HANDLE g_hFile;                    //リソースファイルのグローバルハンドル

HMODULE g_hExe;                    //EXEファイルのハンドル(解説:正しくはDLL等を含む実行可能ファイル、ですね。)
TCHAR szBuffer[MAX_PATH];        //infoファイル用プリントバッファ(260です。オリジナルは"80"という定数です。)
DWORD cbWritten;                //resource infoファイルへの書き込みサイズ
size_t cbString;                //szBufferの文字列長(32bitではunsigned int(4bit)、64bitではunsigned long(8bit)
HRESULT hResult;

//main関数(エントリーポイント)-解説:引数無しmain関数を付加しました。
int main() {
    //対象ファイル名の指定(解説:二つの実行可能ファイルでテストしました。)
    //lstrcpy(g_FileName, TEXT("C:\\Users\\(省略)\\TextToSpeech\\Debug\\TextToSpeech.exe"));
    lstrcpy(g_FileName, TEXT("C:\\Users\\(省略)\\ResourceList\\Debug\\ResourceList01.exe"));
    //リソースを列挙するEXEファイルをロードする
    g_hExe = LoadLibrary(TEXT(g_FileName));
    if(g_hExe == NULL) {
        //(読み込み失敗時の処理を記述)
        printf("実行可能ファイルを読み込めません。");
        getch();    //コンソールを閉じない為
        return 1;
    }
    //リソース情報を収容するファイルを開く
    lstrcpyn(TEXT(g_ResFileName), TEXT(g_FileName), lstrlen(TEXT(g_FileName)) - 3);    //".exe"は4文字だが、lstrcpynはNULLを含むので(-4 + 1)となる
    lstrcat(TEXT(g_ResFileName), TEXT("_resinfo.txt"));
    g_hFile = CreateFile(TEXT(TEXT(g_ResFileName)),    //リソースファイル名
        GENERIC_READ | GENERIC_WRITE,                //アクセスモード
        0,                                            //シェアモード
        (LPSECURITY_ATTRIBUTES) NULL,                //デフォルトセキュリティ
        CREATE_ALWAYS,                                //フラグの作成
        FILE_ATTRIBUTE_NORMAL,                        //ファイルアトリビュート
        (HANDLE) NULL);                                //テンプレート無し
    if(g_hFile == INVALID_HANDLE_VALUE) {
        //ErrorHandler(TEXT("リソースファイルを作成できませんでした。"));-解説:↓の通り単なるprintf文にしました。
        printf(TEXT("リソース情報ファイルを作成できませんでした。"));
        getch();    //コンソールを閉じない為
        return 1;
    }
    //ロードされたファイルのすべてのリソースを読み込む(解説:単にwsprintfと同じ処理なので、このコメントは正しくないですね。)
    hResult = StringCchPrintf(szBuffer, sizeof(szBuffer) / sizeof(TCHAR),
        TEXT("%s\r\nのファイルには以下のリソースがあります。\r\n"), g_FileName);
//        TEXT("このファイルには以下のリソースがあります。\r\n"));(解説:ファイル名を表示させました。)
    if(FAILED(hResult)) {
        //(読み込み失敗時の処理を記述)
        printf("実行可能ファイルのリソースを読み込めません。");
        getch();    //コンソールを閉じない為
        return 1;
    }
    hResult = StringCchLength(szBuffer, sizeof(szBuffer) / sizeof(TCHAR), &cbString);
    if(FAILED(hResult)) {
        //(読み込み失敗時の処理を記述)
        printf("文字列が指定した長さを超えました。");
        getch();    //コンソールを閉じない為
        return 1;
    }
    WriteFile(g_hFile,            //resource infoを書き込むファイルハンドル
        szBuffer,                //書き込みバッファー
        (DWORD)cbString,        //szBufferの書き込みサイズ
        &cbWritten,                //書き込みバイト数
        NULL);                    //オーバーラップドI/Oは無し
    EnumResourceTypes(g_hExe,            //読み込みモジュールのハンドル
        (ENUMRESTYPEPROC)EnumTypesFunc,    //コールバックファンクションアドレス
        0);                                //補足パラメーター
    //ロードして、リソースを列挙したた実行可能ファイルを
    //アンロードし、リソース情報ファイルを閉じる。

    FreeLibrary(g_hExe);
    CloseHandle(g_hFile);
    printf("リソース情報ファイルを書き出しました。");
    getch();                    //コンソールを閉じない為
    return 0;
}

//リソース種類取得用コールバック関数
//EnumTypesFunc(HANDLE, LPSTR, LONG)

BOOL EnumTypesFunc(HMODULE hModule, LPTSTR lpType, LONG lParam) {
    TCHAR szBuffer[MAX_PATH];        //リソース情報用印字バッファー
    DWORD cbWritten;        //リソース情報ファイル書き込みバイト数
    size_t cbString;
    HRESULT hResult;

    //リソース情報ファイルにリソース種類を書き込む
    //種類は文字列または符号なし整数であり、事前にテストを行う

    if(!IS_INTRESOURCE(lpType)) {
        hResult = StringCchPrintf(szBuffer, sizeof(szBuffer) / sizeof(TCHAR), TEXT("種類:%s\r\n"), lpType);
        if (FAILED(hResult)) {
            //(読み込み失敗時の処理を記述)
            return FALSE;
        }
    }
    else
    {
        hResult = StringCchPrintf(szBuffer, sizeof(szBuffer) / sizeof(TCHAR), TEXT("種類:%u\r\n"), (USHORT)lpType);
        if (FAILED(hResult)) {
            //(読み込み失敗時の処理を記述)
            return FALSE;
        }
    }
    hResult = StringCchLength(szBuffer, sizeof(szBuffer)/sizeof(TCHAR), &cbString);
    if(FAILED(hResult)) {
        //(読み込み失敗時の処理を記述)
        return FALSE;
    }
    WriteFile(g_hFile, szBuffer, (DWORD) cbString, &cbWritten, NULL);
    //同種のリソースを総て検索する
    EnumResourceNames(hModule, lpType, (ENUMRESNAMEPROC)EnumNamesFunc, 0);
    return TRUE;
}

//リソース名取得用コールバック関数
//EnumNamesFunc(HANDLE, LPSTR, LPSTR, LONG)

 BOOL EnumNamesFunc(HMODULE hModule, LPCTSTR lpType, LPTSTR lpName, LONG lParam) {
    TCHAR szBuffer[MAX_PATH];        //リソース情報用印字バッファー
    DWORD cbWritten;        //リソース情報ファイル書き込みバイト数
    size_t cbString;
    HRESULT hResult;

    //リソース情報ファイルにリソース名を書き込む
    //リソース名は文字列または符号なし整数であり、事前にテストを行う

    if(!IS_INTRESOURCE(lpName)) {
        hResult = StringCchPrintf(szBuffer, sizeof(szBuffer) / sizeof(TCHAR), TEXT("\tリソース名:%s\r\n"), lpName);
        if(FAILED(hResult)) {
            //(読み込み失敗時の処理を記述)
            return FALSE;
        }
    }
    else
    {
        hResult = StringCchPrintf(szBuffer, sizeof(szBuffer) / sizeof(TCHAR), TEXT("\tリソース名:%s\r\n"), (USHORT)lpName);
        if(FAILED(hResult)) {
            //(読み込み失敗時の処理を記述)
        }
    }
    hResult = StringCchLength(szBuffer, sizeof(szBuffer) / sizeof(TCHAR), &cbString);
    if(FAILED(hResult)) {
            //(読み込み失敗時の処理を記述)
        return FALSE;
    }
    WriteFile(g_hFile, szBuffer, (DWORD)cbString, &cbWritten, NULL);
    //同種のリソースの言語を総て検索する
    // Find the languages of all resources of type

    EnumResourceLanguages(hModule, lpType, lpName, (ENUMRESLANGPROC)EnumLangsFunc, 0);
    return TRUE;
}

//リソース言語取得用コールバック関数EnumLangsFunc(HANDLE, LPSTR, LPSTR, WORD, LONG)
BOOL EnumLangsFunc(HMODULE hModule, LPCTSTR lpType, LPCTSTR lpName, WORD wLang, LONG lParam) {
    HRSRC hResInfo;            //リソース情報ハンドル
    TCHAR szBuffer[MAX_PATH];        //リソース情報用印字バッファー
    DWORD cbWritten;        //リソース情報ファイル書き込みバイト数
    size_t cbString;
    HRESULT hResult;

    hResInfo = FindResourceEx(hModule, lpType, lpName, wLang);
    //リソース情報ファイルへリソース情報を書き込む
    hResult = StringCchPrintf(szBuffer, sizeof(szBuffer) / sizeof(TCHAR), TEXT("\t\tリソース言語: %u\r\n"), (USHORT) wLang);
    if(FAILED(hResult)) {
        //(読み込み失敗時の処理を記述)
        return FALSE;
    }
    hResult = StringCchLength(szBuffer, sizeof(szBuffer) / sizeof(TCHAR), &cbString);
    if(FAILED(hResult)) {
        //(読み込み失敗時の処理を記述)
        return FALSE;
    }
    WriteFile(g_hFile, szBuffer, (DWORD) cbString, &cbWritten, NULL); 
    //リソースハンドルとサイズをバッファーに書き込む
    hResult = StringCchPrintf(szBuffer, sizeof(szBuffer) / sizeof(TCHAR), TEXT("\t\tリソースハンドル:%lx、サイズ:%lu\r\n\r\n"), hResInfo, SizeofResource(hModule, hResInfo));
    if(FAILED(hResult)) {
        //(読み込み失敗時の処理を記述)
        return FALSE;
    }
    hResult = StringCchLength(szBuffer, sizeof(szBuffer) / sizeof(TCHAR), &cbString);
    if (FAILED(hResult))
    {
        //(読み込み失敗時の処理を記述)
        return FALSE;
    }
    WriteFile(g_hFile, szBuffer, (DWORD)cbString, &cbWritten, NULL);
    return TRUE;
}

 

全体を概観すると、対象となる実行可能プログラムをロードすると、リソース情報ファイルを作成して書き込みを開始します。情報は専用のコールバック関数を使い、種類(Type)、名前(Name)、言語等(Language etc)を順次出力ファイルに書き込んでゆき、書き込みが完了すると、ファイルを閉じ、ロードした実行可能プログラムを開放して、main関数を終了します。出力されたリソース情報ファイルは次のようになります。

 

ではでは。

 

ps. 色付けが結構面倒なんで、次回からは変更部分とか、既に開設した所は省く等、手を抜く可能性があります。(笑;)