コマンドIDの範囲

[戻る]
亀山 2016/06/13(月) 10:35:33
今さらかもしれませんが、
メニューのコマンドIDについて質問させていただきます。

昔MFCを使い始めたころから、
メニューやツールバーのコマンドに使用するコマンドIDは、
32768からにする必要があると教えられてきました。
実際、下記のテクニカルノートも、そのように書かれています。
https://msdn.microsoft.com/ja-jp/library/t2zechd4.aspx

ところが、上記のafxribbonres.hの中を見てみると、
16000〜18000の中に、メニューに使用されるコマンドIDも入っています。
そうなると、この32768からという制限は
どのような理由で入っているのかがわかりません。

テクニカルノート内の
「値の有効範囲は次のような理由から決定されます」の部分を読んでも、
コマンドIDの範囲が32768からとされている理由は読み取れませんでしたし、
実際、適当に32768未満の値を使ってみても、問題なく動いているように見えます。

どなたか、このへんの理由をご存じかたはいらっしゃいませんでしょうか。
(実は16bit時代の制限で、今は無意味とか?)

仲澤@失業者 2016/06/13(月) 13:27:46
掲載されたWebページの内容は「MFCを使う場合には」という条件付きですね。
純粋なWin32APIのみを使用する場合の、本質的条件は以下の通りです。

0.前提条件、以下の制約はWM_COMMANDメッセージを用いて、
  Win32APIの制限にのっとって、メッセージを送付する場合に課される。
1.IDは「unsigned short」の範囲でなければならない。

以上です。この条件はWindows2.1〜現行バージョンまで変化はありません。従って、

2.IDを32768以降を使用すべきであるという制限は、
 Win32API自体の制限とは「無関係」です。
3.IDに0を用いないのは「MFCの気分」です
 (IDOKが1等100未満の値は汎用的に使用されていることが多いので避けただけで、
 技術的根拠はありません)。

このような制限がある理由はWM_COMMANDメッセージの仕組みにあります。
このメッセージはあるWndから自身を含む別のWndにIDを送信するのが主眼です。
その時にWPARAMとLPARAMは特定の意味を持ちます。
まず、WPARAM(=32bit)は上位16bitによって、WPARAMの下位16bitと、LPARAMの意味がコン
トロールされます。
HIWORD(WPARAM)を上位、LOWOARD(WPARAM)を下位とすると以下の通りです。

4.HIWORD(WPARAM)==0の時は 、LOWOARD(WPARAM)=メニューID、LPARAM=NULL(=0)
5.HIWORD(WPARAM)==1の時は 、LOWOARD(WPARAM)=アクセラレータID、LPARAM=NULL(=0)
6.HIWORD(WPARAM)が上記以外の場合は「報告コード」、
  このときLOWOARD(WPARAM)は「コマンドID」、また、LPARAMは送信者のHWND。

となります(WM_COMMANDのマニュアル参照)。

つまり、IDは、WPARAMの下位WORDに設定しなければならないので、16bit以内に抑えなけ
れば正常に伝達できないわけですね。

ちなみに、「報告コード」とはボタンの場合BN_CLICKED等、エディットの場合EN_SELCHAN
GE等のコードを言い、
対象のコントロールによって異なります。
かつ、これらの制限の全ては、Windowsが自身に課したものであって、これに従わないと
、
Windowsが提供する標準コントロールは動きませんよ。という定義にすぎません。

プログラマが独自に実装したコントロールやウインドウにWM_COMMANDを送付する場合は制
限はありません。
ただしこの場合OnCommand()をオーバーライドして、WPARAMを自身で解決しなければなり
ません。

さて、重要なのは、以上の話は「WM_COMMANでIDを送付する場合の話」に過ぎません。

IDには他のものが沢山あり、その中でリソースを識別するIDがあります。
リソースIDの制限は64bitです。
混乱するのは、ダイアログリソースの様に、リソースの記述にWM_COMMANDで
送付されるコマンドIDが、記述されてしまうことですね。つまり、

7.リソースの定義上、コマンドIDは32bitまで許可されるが、
 それをWM_COMMANDで送付するときは16bit分しか送られませんよ。
  しかもDLGのIDの様に事実上64bitのリソースIDもOKなものまである
 (コマンドでは無いのでOKなのですね)。

ということになるわけです。これは、ボタンなどのコントロールは、
自身に発生したクリック等のイベントをWM_COMMANDを使用して、
自身のウインドウIDを指定して送付してくるという、仕様が原因です。
ウインドウIDは、DWORD(32bit)が指定可能なのですが、
コマンドとして送付できるのは16bitというわけです。

MFCではこの混乱を避けるため、リソースIDの種類ごとに
「MFC上のリソースIDの使用範囲」をリソースの種別毎に定義して、予約したわけです。
この時予約を0x0001〜0x7FFFの範囲に設定し、
0x8000以降をユーザー用にとっておいたものと思われます。
この定義にのっとって「リソースエディタ」がIDを自動的に採番するわけですね。

さて、結論としては、

(質問)「コマンドに使用するコマンドIDは、32768からにする必要がある?」
(回答) MFCでは可能な限り従うべきですが、本質的にはありません。
  MFCでは0x8000〜0xDFFFが使用可能となってます(安全に使用可能の意味)。
  MFC及び標準コントロール「以外」のウインドウに設定または送付する場合は、
  WORDであればなんでもOKです。
  ただし、IDOK=1 IDCANSEL=2等とぶつからないようにすれば十分です。
    おそらくコンフリクトを恐れるあまり「面倒なので0x8000以降を使ってくれ」
  ということからきていると思われます。

以上です。

仲澤@失業者 2016/06/13(月) 13:47:49
こめん、リソースのIDは32bitまでですね。
該当する、64bitという部分は32bitと読み替えてください。orz.

追加として、リソース内のコントロールに設定するIDは厳密には
「コントロールID」または「ウインドウID」に該当します。
このIDの内の下位16ビットが「コマンドID」としてWM_COMMANDのWPARAMの
下位WORDに設定されます。

亀山 2016/06/13(月) 16:48:34
[[解決]]
詳しい解説ありがとうございます。

ダイアログ上のコントロールは8〜0xDFFFまで広く使えるのに、
メニューのコマンドだけは0x8000〜0xDFFFになっているのは、
特に制限上の理由があるわけではないということですね。

> 文字列 ID の 0x8000 〜 0xFFFF はコマンドのメニュー プロンプトとして予約されて
います。

と書いてあるので、
コマンド用の文字列とその他の文字列を分けるためもあるのかもしれません。

それにしても、この16000〜18000の範囲(afxribbonres.h)は一貫性がないですね。
BCGSoft社のソースを組み込む際に変にクラス名を変える余裕があるなら、
ちゃんとリソースIDを正しい予約範囲内に移しておいてもらいたかったものです。

仲澤@失業者 2016/06/13(月) 18:29:59
リボンは複雑なウインドウなので結構な量のリソースを使用します。
当該の範囲のほとんどは、コマンドでないIDや単なるリソースIDの様です。

本質的には、操作を意味するIDとコントロールのウインドウIDは
別物として扱うべきですので、当該の範囲のIDを別のウインドウで
使用したからといって障害が発生するとは考えられません
(そういう設計はしないのが普通)。
なので、たぶん、使用しても問題ないと考えられます。

もしダメな場合は、「きわめて設計が悪い」と判断されてしまいます。
つまりリボンはリスクの高い部品であり、特段の事情がないかぎり、
使用すべきではないと判断されかねない。ということになるはずですね。

亀山 2016/06/13(月) 19:46:05
> 当該の範囲のIDを別のウインドウで
> 使用したからといって障害が発生するとは考えられません

たとえばの話ですが、
SDIアプリケーションのプロジェクトの中に作成したダイアログの
リソースID(IDD)が16104になっていると、
ツールバーを右クリックして「カスタマイズ」を選んだ後にアサートが出て、
MFCの中にあるカスタマイズ用ダイアログを出せなくなってしまいます。

MFC拡張DLLでも作らない限り、
通常はこんな値が自動採番されることはないでしょうけど、
もともとは有効範囲として公表されていた範囲なのだから、
afxribbonres.hの値も逃がしておいてくれよと思いました。

仲澤@失業者 2016/06/14(火) 11:09:37
>ツールバーを右クリックして「カスタマイズ」を選んだ後にアサートが出て、
>MFCの中にあるカスタマイズ用ダイアログを出せなくなってしまいます。

そうですが。それは深刻ですね。

通常、異なる種類のリソースのID同士なら、同じ値が許されています。
例えば、文字列のリソースIDとアイコンのリソースIDが同じであってもかまいません。
なぜなら、リソースをロードするときに(内部的に)使用する関数FindResource()は、

1.対象リソースが含まれる実行モジュールのインスタンスハンドル(EXE又はDLL)
2.対象リソースの種別コード(RT_STRING、RT_DIALOG等)
3.対象リソースの名称文字列、又はMAKEINTRESOURCE(WORD整数、つまりID)

の引数をとります。種別コードRT_xxxxが異なればIDが同じであってもロード可能なわけ
ですね。
さらに、リボン等のコントロールはDLLで供給されているはずで、1.のハンドルも異なり
ます。
混同されるようには思えないのですが、いかがでしょう。

さて、以上を踏まえてまず、「カスタマイズ」を選択した時の問題ですが、
このメニュー項目の文字列ですが、文字列リソースの

   IDS_AFXBARRES_PROPSHT_CAPTION "カスタマイズ"

が該当しそうです。プロパティシートのキャプションである様に読めます。
これはafxribbonres.hヘッダーでは

#define IDS_AFXBARRES_PROPSHT_CAPTION   16103

のように定義されています。問題になっている16104は直下に

#define IDD_AFXBARRES_PROPPAGE1         16104

とありますので、多分プロパティページ(DLG)のIDかもしれません。

以上から勘案すると、IDD_AFXBARRES_PROPPAGE1のIDを持つDLGを
リソースからロードしようとしているのですが、
「誤って」EXEのリソースからロードしようとしている様に思えます。
当たり前ですが、リボンのプロパティページのダイアログリソースは、
リボンコントロールを提供するDLL内にあるはずです。

これから察するに、何か、根本的な誤りがあるような匂いがします。
現在の情報で言えることは以上の通りです。

古いスレッドには追加発言できません。