ボンジュール・マドモアゼル

本サイトの情報は自己責任にてご利用下さい。

[VC] リソースファイル Visual C++独自仕様について

 
※以下、内容が正確かどうかはわかりません。誤りがあった場合には一報頂けると助かります。
■ブロック構成
Visual C++ のリソースファイルは、リソースエディタとリソースコンパイラのふたつのアプリケーションによってアクセスされる。これらアプリケーションに対して、リソース定義のアクセス制御を行うため、リソースファイルはシンボル定義によってブロック化される。ブロックは以下のものがある。

※ブロックの名前は正式なものではない、説明のための便宜的なもの。


(1) APSTUDIO_READONLY_SYMBOLS ブロック
#define APSTUDIO_READONLY_SYMBOLS 〜 #undef APSTUDIO_READONLY_SYMBOLS で括られるブロック。このブロックは、リソースコンパイラ、リソースエディタの両方から参照される領域だが、リソースエディタは、シンボル定義を目的として、#include, #define, #ifdef 等のディレクティブのみを解釈し、それ以外のコードを無視する。
このブロックで定義されるシンボル名および定義値は、リソースエディタで変更することはできない。ただし、例外として、resource.h などの読み書き可能なヘッダーファイルをこのブロックでインクルードしている場合、そのファイルで定義されるシンボルはリソースエディタで変更できる。

(2) APSTUDIO_INVOKED ブロック
リソースエディタでのみ有効なブロック、リソースコンパイラ時には無視される。主にTEXTINCLUDE リソースを定義するためのブロックである。

(2.5) APSTUDIO_HIDDEN_SYMBOLS (図にはない、おまけ)
#define APSTUDIO_HIDDEN_SYMBOLS 〜 #undef APSTUDIO_HIDDEN_SYMBOLS で括られたブロック。このブロック内で定義されたシンボルは、リソースエディタのシンボルブラウザには表示されない。また、リソースエディタ上、このブロックで定義されたリソースIDはシンボル表記の箇所が整数値表記となる。

(3) Non-APSTUDIO_INVOKED ブロック
#ifndef APSTUDIO_INVOKED 〜 #endif で括られるブロック。このブロック内の文字列は、リソースエディタでは無視され、リソースコンパイル時のみ適用される。

(4) リソースコンパイラとリソースエディタの両方でアクセスするブロック
上記以外の領域は、リソースコンパイラとリソースエディタの両方でアクセスされる。


VCリソースファイルの構成



■ TEXTINCLUDE リソース

APSTUDIO_READONLY_SYMBOLS ブロックおよび、Non-APSTUDIO_INVOKED ブロック内で定義したコードは、同一の内容を、APSTUDIO_INVOKED ブロック内に TEXTINCLUDE リソースとして特別な記法で定義する必要がある。なぜ、同じ内容をわざわざ TEXTINCLUDE リソースとして定義する必要があるのか。それは、ブロック内に書かれるべきコードをリソース化し、.apsファイルにロードするためである。
このことを理解するのは、リソースファイルの読込みから保存までのデータの流れを追ってみるのが早い。下図のとおり、リソースファイルのオープン時、APSTUDIO_READONLY_SYMBOLS ブロックで定義されたシンボルが、.aps ファイルに読み込まれ、TEXTINCLUDEリソースは、リソースとして、.aps ファイルに取り込まれる。そして、リソースファイルの保存時には、ロードされた .aps ファイル 内の TEXTINCLUDE リソースが、各ブロックへ保存される。したがって、リソースエディタでリソースファイルを保存したときには、APSTUDIO_READONLY_SYMBOLS ブロックおよび、Non-APSTUDIO_INVOKED ブロック内にもともと記述されたコードは保存後のファイルには直接、引き継がれないのである。

リソースファイルデータフロー



TEXTINCLUDE リソースとは、リソースファイル上の文字列を、apsファイルに引き継ぎぐため、リソース化したものである。TEXTINCLUDE リソースは、1, 2, 3 の各リソースIDによって動作が異なり、各々の動作は以下のとおりとなる。

1 TEXTINCLUDE
新規リソースIDを保存するファイル名を指定する。このファイルには新規リソースに割り振られる次の番号なども保存される。このリソースが未定義の場合、resource.h がそれらの情報を書き込むファイルとなる。通常、このファイルで定義済みのシンボルは、その整数値をリソースエディタ上で変更することができる。リソースエディタでリソースファイルを保存すると、ここで指定したファイル名は、次のようにリソースファイルの先頭に展開される。

#include "ファイル名"

なお、このリソースに無効なファイル名が指定されているリソースファイルは、リソースエディタでは保存することができない。MFCの標準リソースファイルは、この制限を利用して、当該ファイルを書き込み不可としている。

2 TEXTINCLUDE
このリソースで定義された文字列は、リソースエディタでリソースファイルを保存する時、APSTUDIO_READONLY_SYMBOLS ブロック内に保存されるデータ元となる。通常、このリソースでは、読み取り専用シンボル、もしくは、読み取り専用のシンボル定義を含むファイルをインクルードするディレクティブを定義する。

3 TEXTINCLUDE
このリソースで定義された文字列は、リソースエディタでリソースファイルを保存する時、Non-APSTUDIO_INVOKED ブロック内に保存されるデータ元となる。通常、このリソースでは、リソースコンパイル時にインクルードしたいファイルなど、リソースコンパイル時のみ有効にしたいコードを記述する。

テクニカルノート35についての蛇足

話の発端は、リソースは、ひとつのプロジェクトに対して、ひとつの翻訳単位のリソースファイルとして構成する必要性にある。

[VC] Visual Studio が include したリソースファイルをマージすることの検証

 
Visual C++ が読み込む .RC ファイルの中で、コンパイル時ディレクティブを使用せずに #include ディレクティブによってほかの .RC ファイルをインクルードしているときは注意してください。従来のようにテキスト エディタを使用して編集した .RC ファイルを Visual C++ に取り込む場合はこの点が問題となります。Visual C++ はインクルードされている .RC ファイルを読み込み、その中に定義されているリソースをインクルード元の .RC ファイルのリソースにマージします。インクルード元の .RC ファイルを保存すると、ファイルの中の #include ステートメントがインクルードされるリソースに置き換えられます。

MFC ライブラリ リファレンス
テクニカル ノート 35: Visual C++ における複数のリソース ファイルとヘッダー ファイルの使用




要は、リソースファイル内に、単に #include で取り込んだ外部ファイルは、リソースエディタでは、展開されてマージされてしまいますよ、ということ。そこで、Visual Studio 6.0 で実際に検証してみた。

1. 外部ファイルと、それをインクルードしたリソースファイルを用意する


外部ファイル subres.rc2
STRINGTABLE DISCARDABLE

BEGIN
IDS_MYSAMP1 "My Sample String1"
IDS_MYSAMP2 "My Sample String2"
END


リソースファイル(インクルード元)ResTest.rc
...

STRINGTABLE DISCARDABLE
BEGIN
IDS_APP_TITLE "ResTest"
IDS_HELLO "Hello World!"
IDC_RESTEST "RESTEST"
END

#endif // 日本語 resources
/////////////////////////////////////////////////////////////////////////////

#include "subres.rc2"
...


ストリングテーブルをリソースエディタで確認する。
ストリングテーブル確認


2. リソースエディタにてリソースを編集する

あえて、ストリングテーブルとは関係ないダイアログを編集し保存する.
編集前ダイアログ編集前


編集後ダイアログ編集後


3. 保存後、リソースファイルを確認する

リソースファイル(インクルード元)ResTest.rc
...

STRINGTABLE DISCARDABLE
BEGIN
IDS_APP_TITLE "ResTest"
IDS_HELLO "Hello World!"
IDC_RESTEST "RESTEST"
IDS_MYSAMP1 "My Sample String1"
IDS_MYSAMP2 "My Sample String2"
END
...
お、ほんとだ。外部ファイルの内容がマージされている。

[Win32 プログラミング大全] warning C4312: '型キャスト' : 'LONG' からより大きいサイズの 'HMODULE' へ変換します。

 
「warning C4312: '型キャスト' : 'LONG' からより大きいサイズの 'HMODULE' へ変換します。」について

153 名前:デフォルトの名無しさん :05/02/20 05:02:21
質問です。
ウィンドプロシージャ関数内でダイアログを呼び出そうとして
DialogBox(GetWindowInstance(hWnd),MAKEINTRESOURCE(IDD_MYDIALOG), hWnd,DlgProc);
としました。すると以下のような警告が出てしまいました。
warning C4312: '型キャスト' : 'LONG' からより大きいサイズの 'HMODULE' へ変換します。
どうやらGetWindowInstance関数でインスタンスハンドルを取得した場合に出るようなのですが

その先の改善の仕方がどうにもわかりません、この警告を消す方法を教えてください、お願いします。

155 名前:デフォルトの名無しさん :05/02/20 06:46:57
>>153
Win64への以降を睨んで、VCやWindows.hは64bitへ対応する準備がなされている。
まずWin64ではハンドル・ポインタが64bitになるのに、int/longは32bitのまま。
だからハンドル・ポインタを格納する整数型にint/longを使っているとまずいので、
そういう警告が出る。(ハンドル・ポインタ→int/longも警告が出る)

http://pc5.2ch.net/test/read.cgi/tech/1108815602/32
ハンドル・ポインタを整数に格納するときは*_PTRを使うことになっている。
すると64bit対応したと見て警告が出なくなる。

今回のGetWindowInstanceは古いものだとGetWindowLongを呼び出すマクロだが、
最近の物だとGetWindowLongPtrを呼び出すように変更されている。
PlatformSDKをアップデートしてみろ。(直接書き替えても別に悪くは無いが)

182 名前:153 :05/02/20 22:38:40
>>155
ありがとうございます。ためになりました!!
スタートページからPlatformSDKをアップデートしようとしたんですが
更新できる項目がなにもなかったのでwindowsX.hの中を見ていたら
GetWindowInstace関数はGetWindowLongPtr関数を呼び出すように
書かれていました。
それでもこの警告が出てしまうので、実行時には今のところ問題なさ
そうだし、この警告を消すのはあきらめることにしました。

>>182
Win32ではGet/SetWindowLongPtrは単にGet/SetWindowLongを呼び出すマクロになっているだけだからどうしようもない

220 名前:182 :05/02/22 10:05:33
おはようございます
>>153の警告を消すのをあきらめるとか言っておい
てやっぱりあきらめられず色々やってました。
結局、WinUser.hというヘッダーファイルにたどり着き
そこにGetWindowLong関数が記述してあったので、
戻り値がLONGとなっていたのをLONG_PTRに書き変えて
リビルドすると警告はきれいさっぱり消えてくれました。


結果目的は達成することができました!ですが最後に質問させてください
勝手に上のようにヘッダーファイルを書き換えてしまったのですが
それによって不具合が生じることなどが考えられるでしょうか?
よろしくお願いします。

221 名前:デフォルトの名無しさん :05/02/22 10:10:11
それなら戻り値をLONG_PTRにキャストしてやれば良いんじゃね?

222 名前:182 :05/02/22 10:25:03
どうもです
戻り値をLONG_PTRにキャストするとLOGN_PTRをHINSTANCEに
変換できないという旨のエラーが出てしまいました。
それと書き換えてない状態でGetWindowInstance関数が戻すの
はあくまでLONG型のデータらしくそれをキャストしても同じ旨の
警告がでるみたいです・・

240 名前:デフォルトの名無しさん :05/02/23 17:56:03
>>220
>>199
それと221はたぶんGetWindowInstanceを一旦LONG_PTRにキャストしてから HINSTANCEにキャストするように直せということじゃないか?

249 名前:182 :05/02/23 23:04:13
>>240
そっか・・そうですよね!気づきませんでした
そのことをまったく考えてなかったです。
目が覚めました。ありがとうございます!
★初心者にVisual C++を教えるスレ★ Part17


そこで、

winuser.h の GetWindowLong関数の戻り値を LONG から LONG_PTR に修正してみる
WINUSERAPI
LONG LONG_PTR

WINAPI
GetWindowLongW(
__in HWND hWnd,
__in int nIndex);

これは確かに警告が消えた。次に

GetWindowInstance関数の戻り値を LONG_PTR にキャストして、HMODULE にキャストしてみる

HINSTANCE hinst = (HMODULE)(LONG_PTR)GetWindowInstance(hwnd);
HINSTANCE hinst = LongToHandle(GetWindowInstance(hwnd));

残念ながらこれは警告が消えなかった。しかし、次のように、windowsx.h に細工をしてやると警告を消すことができた。
#define GetWindowInstance(hwnd) ((HMODULE)LongToHandle(GetWindowLongPtr(hwnd, GWLP_HINSTANCE)))


なお、LongToHandle は、basetsd.h で次のように定義されている。
#define LongToHandle( h ) ((HANDLE)(LONG_PTR) (h) )


(参考) GetWindowLongPtr について msdn より.

GetWindowLongPtr
指定されたウィンドウに関する情報を取得します。ウィンドウの拡張ウィンドウメモリ内の指定されたオフセット位置にある値もこの関数を使って取得できます。
この関数は、GetWindowLong 関数の改訂版です。32 ビット版 Windows と 64 ビット版 Windows の両方ともと互換性のあるコードを記述するには、GetWindowLongPtr 関数を使ってください。

msdn プラットフォーム SDK GetWindowLongPtr

[Windows] Windows 変換キーを無効とする

 
Windows で変換キーを無効とするには、以下のいくつかの方法がある。

ここでは、IME のキー設定にて無効とする方法を示す。

1. IME のプロパティを開き、「設定」ボタンをクリックする。

IME キー設定1


2. 変換キーの行の「入力/変換済み文字なし」の列にあたる箇所をダブルクリックする。

IME キー設定2



3. - を選択し、機能を未設定とする。

IME キー設定3


以上

なお、レジストリのスキャンコードマップの作成については次のサイトが参考になる。

[その他] 高速カーソル移動と編集 設定ファイル(窓使いの憂鬱版)

 
思考の速度でパソコンを使う技術で紹介されたキーボードカスタマイズを窓使いの憂鬱で実現してみた。
モーダル操作(モード切替)には、&Toggle ステートメントを利用している。なお、コマンドモード移行キーには、変換キーを割り当てているが、変換キーを押下した際、日本語入力モードにならないようにする必要がある。これについては Windows 変換キーを無効とする を参照のこと。
include "109.mayu" # 109 キーボード設定

def alias SMC = Semicolon
keyseq $MODE_OFF = &Toggle(Lock0,off) &Toggle(Lock1,off)

#Command Mode

    key L0-A    = C-←
    key L0-S    = ←
    key L0-D    = →
    key L0-F    = C-→

    key L0-J    = Home
    key L0-K    = ↑
    key L0-L    = ↓
    key L0-SMC  = End

    key L0-U    = PageUp
    key L0-I    = &Variable(0, 10) &Repeat((↑))
    key L0-O    = &Variable(0, 10) &Repeat((↓))
    key L0-P    = PageDown

    key L0-Z    = C-Z
    key L0-X    = C-X
    key L0-C    = C-C
    key L0-V    = C-V

#Select Mode

    key L1-A    = S-C-←
    key L1-S    = S-←
    key L1-D    = S-→
    key L1-F    = S-C-→

    key L1-J    = S-Home
    key L1-K    = S-↑
    key L1-L    = S-↓
    key L1-SMC  = S-End

    key L1-U    = S-PageUp
    key L1-I    = &Variable(0, 10) &Repeat((S-↑))
    key L1-O    = &Variable(0, 10) &Repeat((S-↓))
    key L1-P    = S-PageDown

    key L1-Z    = C-Z &Toggle(Lock1,off)
    key L1-X    = C-X &Toggle(Lock1,off)
    key L1-C    = C-C &Toggle(Lock1,off)
    key L1-V    = C-V &Toggle(Lock1,off)

#Changing Mode

    key *変換   = &Toggle(Lock0,on)
    key L0-G    = &Toggle(Lock1)
    key L0-Space   = $MODE_OFF
    key L1-Space   = $MODE_OFF
ところで、これを実装している最中、「高速カーソル移動と編集」の操作説明に一部不備があることに気がついた。
操作説明を見ると3番の操作で、[G]キーを押して範囲選択しているが、選択範囲の開始地点はどこになるのだろうか。コマンドモードに入った時点のカーソル位置がそうなのだろうか。
だとすれば、コマンドモードに入る前にカーソルを適切な位置に移動させなければならない。だが、カーソル移動はコマンドモードに入らなければならない。
もし操作説明に誤りがなければ、たかが範囲選択に以下のような周りくどい操作が必要になる。
  1. コマンドモードに入る
  2. 選択範囲の開始位置までカーソルを移動
  3. コマンドモードを抜ける
  4. 再度コマンドモードに入る
  5. 選択範囲の終了位置までカーソルを移動させる
  6. [G]キーを押下
あと、AutoHotKey におけるカスタマイズ事例があったので紹介しておく。
「思考の速度でパソコンを使う技術」をAutoHotKeyで作ってみる