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 リソースの利用方法について説明しました。