STRINGTABLE リソースと LoadString を使った文字列テーブルリソースの利用

キャプション等の文字列をリソースファイルに格納する

キャプションをリソースファイルに格納して、それをプログラムから利用しましょう。出来上がりは次のようになります。


図1. プログラムのタイトルとメッセージボックスのメッセージがリソースファイルから読み込んだ日本語に変っている

リソーススクリプト (*.rc) で定義できるリソースタイプには、MENU (メニュー) や ICON (アイコン) の他に、STRINGTABLE (文字列テーブル)というのもあります。 STRINGTABLE リソースには、文字列を格納します。

STRINGTABLE リソースは下図のように書きます。STRINGTABLE というリソース名に続き、一行ごとに ID と文字列を書きます。 プログラムからはこの ID を使ってそれぞれの文字列にアクセスできます。


図1. STRINGTABLE リソースの書き方

文字列テーブルリソースを利用する ~ LoadString 関数

プログラムからこのリソースを読み込む場合は、LoadString 関数を使います。

int LoadString(
	HINSTANCE hInstance,
	UINT uID,
	LPTSTR lpBuffer,
	int nBufferMax
);

ここで
hInstance  インスタンスハンドル
uID        リソース ID
lpBuffer   バッファへのポインタ (TCHAR 文字列)
nBufferMax バッファの TCHAR でのサイズ

hInstance は今はプログラムに埋め込むリソースを利用するので、GetModuleHandle 関数を使って、GetModuleHandle(NULL) で取得できます。 (WinMain では引数に渡されてくるのでそれを利用すればよいでしょう)。

例えば次のようにします。

TCHAR szMsg [256];
LoadString ( 
	GetModuleHandle(NULL), 
	IDS_MSG_QUIT, 
	szMsg, 
	sizeof(szMsg)/sizeof(szMsg[0]) );

適当なサイズのバッファ szMsg を用意しておき、そこに ID が IDS_MSG_QUIT (ヘッダファイルで 3002 という定数として定義) である文字列のリソースを読み込みます。

sizeof(szMsg)/sizeof(szMsg[0]) のところは C 言語の復習になりますが、これで配列の要素数を取得できます。 今ここでは szMsg を TCHAR 型の配列と考えれば、sizeof(szMsg) は配列全体のバイト数でのサイズが取得でき、 sizeof(szMsg[0]) は配列の要素一個分のサイズが取得できます。それの割り算ですから、TCHAR の要素数が取得できます。

ここで 256 と直書きしないで、わざわざ計算しているのは安全性のためです。例えばある時点で、 szMsg というバッファのサイズを 256 と定義したので、LoadString に 256 と書きました。その後、バッファのサイズを小さくして、 TCHAR szMsg [128] と定義しなおしたとします。ここで忘れずに LoadString に渡していたバッファサイズも 128 に書き換えれば問題は起きないのですが、うっかりそのサイズを書き忘れたとします。 そうすると、プログラムでは TCHAR で 128 個分のバッファしか用意していないのに、 TCHAR で 256 個分のデータを読み込んでしまう可能性が出てきてしまいます。 もし 128 より大きいサイズを読み込むと、メモリ領域が壊れてしまいます。(この場合はスタックが壊れます)

このようなポカを避けるために、わざわざ sizeof(szMsg)/sizeof(szMsg[0]) という風にしています。 これはコンパイル時にサイズが確定できる値なので、コード上は計算値になっていますが、実行時には定数として扱われていますから、 「計算」しているからといって、パフォーマンス上の問題はありません。

C 言語のこの辺の話が怪しい人は Cプログラミング専門課程 を読んでください。

ソースコードの変更

resource.h は次のように変更します。赤でマークしたところが以前と異なるところです。

#pragma once

// Application Data
#ifdef DEBUG
#define VER_DEBUG   VS_FF_DEBUG
#define VER_PRIVATE VS_FF_PRIVATEBUILD
#else
#define VER_DEBUG   0
#define VER_PRIVATE 0
#endif

#define VER_STR_COMMENTS         "Windows Programming Primer Sample Program"
#define VER_STR_COMPANYNAME      "keicode.com"
#define VER_STR_FILEDESCRIPTION  "Windows Programming Primer Sample Program"
#define VER_FILEVERSION          1,0,0,1
#define VER_STR_FILEVERSION      "1,0,0,1"
#define VER_STR_INTERNALNAME     "Sample Program - Simple"
#define VER_STR_ORIGINALFILENAME "simple.exe"
#define VER_STR_LEGALCOPYRIGHT   "(C) 2008 Dadosan All Rights Reserved"
#define VER_STR_PRODUCTNAME      "Windows Programming Primer Samples"
#define VER_PRODUCTVERSION       1,0,0,0
#define VER_STR_PRODUCTVERSION   "1,0,0,0"

// Icon ID
#define IDI_APP          100

// Resource ID
#define IDR_VERSION_INFO 1000
#define IDR_MENU_MAIN    1001

// Menu Item ID
#define IDM_FILE_OPEN    2000
#define IDM_FILE_EXIT    2001
#define IDM_HELP_ABOUT   2100

// String ID
#define IDS_APP_TITLE       3000
#define IDS_CAPTION_CONF    3001
#define IDS_MSG_QUIT        3002

resource.rc は以下のように変更します。赤でマークしたところが以前と異なるところです。

#include <windows.h>
#include "resource.h"


//
// Version Information
//


1 VERSIONINFO
FILEVERSION     VER_FILEVERSION
PRODUCTVERSION  VER_PRODUCTVERSION
FILEFLAGSMASK   VS_FFI_FILEFLAGSMASK
FILEFLAGS       ( VER_DEBUG | VER_PRIVATE )
FILEOS          VOS_NT_WINDOWS32
FILETYPE        VFT_APP
BEGIN
    BLOCK "StringFileInfo"
    BEGIN
        BLOCK "040904b0"
        BEGIN
            VALUE "Comments",         VER_STR_COMMENTS
            VALUE "CompanyName",      VER_STR_COMPANYNAME
            VALUE "FileDescription",  VER_STR_FILEDESCRIPTION
            VALUE "FileVersion",      VER_STR_FILEVERSION
            VALUE "InternalName",     VER_STR_INTERNALNAME
            VALUE "OriginalFileName", VER_STR_ORIGINALFILENAME
            VALUE "LegalCopyright",   VER_STR_LEGALCOPYRIGHT
            VALUE "ProductName",      VER_STR_PRODUCTNAME
            VALUE "ProductVersion",   VER_STR_PRODUCTVERSION
        END
    END
    BLOCK "VarFileInfo"
    BEGIN
        VALUE "Translation", 0x409, 1200
    END
END


//
// Icon
//


IDI_APP ICON "app.ico"


//
// Menu
//


IDR_MENU_MAIN MENU 
BEGIN
    POPUP "ファイル(&F)"
    BEGIN
        MENUITEM "開く(&O)...",                 IDM_FILE_OPEN
        MENUITEM SEPARATOR
        MENUITEM "終了(&X)",                    IDM_FILE_EXIT
    END
    POPUP "ヘルプ(&H)"
    BEGIN
        MENUITEM "バージョン情報(&A)...",       IDM_HELP_ABOUT
    END
END


//
// String Table
//


STRINGTABLE 
BEGIN
    IDS_APP_TITLE      "プログラミング徹底解説 - テストプログラム"
    IDS_CAPTION_CONF   "確認"
    IDS_MSG_QUIT       "プログラムを終了してもよろしいですか?"
END

リソース・スクリプトの変更はこれだけです。resource.rc は Unicode で保存するのを忘れないようにしてください。

simple.cpp では次のようにして、上で作成した STRINGTABLE リソースに書いた文字列を利用します。


#include "simple.h"
#include <windowsx.h>
#include "resource.h"


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

	HWND hWnd;
	WNDCLASSEX wcl;
	TCHAR szTitle[256];
	
	wcl.cbSize        = sizeof(WNDCLASSEX);
	wcl.hInstance     = hInstance;
	wcl.lpszClassName = WND_CLASS_NAME;
	wcl.lpfnWndProc   = WindowProc;
	wcl.style         = NULL;
	wcl.hIcon         = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APP));
	wcl.hIconSm       = NULL;
	wcl.hCursor       = LoadCursor(NULL, IDC_ARROW);
	wcl.lpszMenuName  = MAKEINTRESOURCE (IDR_MENU_MAIN);
	wcl.cbClsExtra    = 0;
	wcl.cbWndExtra    = 0;
	wcl.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
	
	if(!RegisterClassEx(&wcl)) {
		return FALSE;
	}

	LoadString( 
		hInstance, 
		IDS_APP_TITLE, 
		szTitle, 
		sizeof(szTitle)/sizeof(szTitle[0]) );
	
	hWnd = CreateWindowEx(
		NULL, 
		WND_CLASS_NAME,
		szTitle,
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, 
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		NULL,
		NULL,
		hInstance,
		NULL);
	
	if(!hWnd) {
		return FALSE;
	}
	
	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);
	
	MSG msg;
	BOOL bRet;
	
	while( ( bRet = GetMessage( &msg, hWnd, 0, 0 ) ) != 0) { 

		if (bRet == -1) {
			break;
		}
		else {
			DispatchMessage( &msg ); 
		}
	}

	return msg.wParam;
	
}


LRESULT CALLBACK WindowProc(
	HWND hwnd,
	UINT uMsg,
	WPARAM wParam,
	LPARAM lParam) {

	switch( uMsg ) {
	HANDLE_MSG( hwnd, WM_CLOSE, Main_OnClose );
	HANDLE_MSG( hwnd, WM_COMMAND, Main_OnCommand );
	HANDLE_MSG( hwnd, WM_DESTROY, Main_OnDestroy );
	HANDLE_MSG( hwnd, WM_PAINT, Main_OnPaint );
	HANDLE_MSG( hwnd, WM_SIZE, Main_OnSize );	
	default:
		return DefWindowProc(hwnd, uMsg, wParam, lParam);
	}

	return 0;
}


void Main_OnClose( HWND hwnd ) {

	TCHAR szMsg [256];
	TCHAR szCaption [256];

	LoadString ( 
		GetModuleHandle(NULL), 
		IDS_MSG_QUIT, 
		szMsg, 
		sizeof(szMsg)/sizeof(szMsg[0]) );
	LoadString ( 
		GetModuleHandle(NULL), 
		IDS_CAPTION_CONF, 
		szCaption, 
		sizeof(szCaption)/sizeof(szCaption[0]) );
	
	if ( IDYES == MessageBox ( 
		hwnd,
		szMsg,
		szCaption,
		MB_YESNO ) ) {

		DestroyWindow(hwnd);

	}
	
}


void Main_OnCommand( HWND hwnd, int id, HWND hwndCtl, UINT codeNotify ) {

	switch( id ) {
	case IDM_FILE_EXIT:
			
		SendMessage ( hwnd, WM_CLOSE, 0, 0 );
		break;
			
	case IDM_HELP_ABOUT:

		::MessageBox ( 
			NULL, 
			TEXT("Hello, About!"),
			TEXT("About This Program"), 
			MB_OK );
			
		break;		
	}
		
}


void Main_OnDestroy( HWND hwnd ) {

	::PostQuitMessage( 0 );

}


void Main_OnPaint( HWND hwnd ) {

	HDC hdc;
	PAINTSTRUCT ps;
	RECT rect;
	
	hdc = BeginPaint ( hwnd, &ps );
	GetClientRect ( hwnd, &rect );
	DrawText( 
		hdc, 
		TEXT("Hello, world!"), 
		-1, 
		&rect, 
		DT_CENTER | DT_VCENTER | DT_SINGLELINE
	);
	EndPaint( hwnd, &ps );

}


void Main_OnSize( HWND hwnd, UINT state, int cx, int cy ) {
	
	InvalidateRect( hwnd, NULL, TRUE );

}

以上、これをいつもの makefile でビルドして実行すれば、冒頭のスクリーンショットのように、 文字列がリソースファイルから読み込まれていることが確認できるはずです。

今回は STRINGTABLE リソースの利用方法について説明しました。

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Web/DB プログラミング徹底解説