none
マウススクロールのWParamがOS環境によって異なる RRS feed

  • 質問

  • お世話になっております。

    WindowsFormアプリにてマウスホイールでパネルがスクロールするよう実装を行っています。
    Windows メッセージのWParamの値を取得して、スクロールの実装をしているのですが
    このWParamの値が環境によって変わってしまい困っております。

    【コード】
     protected override void WndProc(ref Message m) {
     
      int WM_MOUSEWHEEL = 0x20A; //マウスホイールのメッセージ
      
      if (m.Msg == WM_MOUSEWHEEL){
       long wheelDelta = (Int64)m.WParam;
      }
      
       base.WndProc(ref m);
     }
     
    【実施結果】
    マウスホイールを手前に引いたときに以下値が取得されました。
    Windows7…wheelDeltaの値 -7864320
    Windows10 Enterprise…wheelDeltaの値 -7864320
    Windows10 Pro…wheelDeltaの値 4287102976

    マウスホイールを奥に回した場合は全環境同じで7864320でした。

    そもそもマウスホイールのWParamについてあまりわかっていなかったので、自分なりに調べてみたところ、
     ・上位のビットに移動量
     ・手前に動かすと不の値、奥に動かすと正の値が取得できる
    ということはわかりました。
    それと今回の結果を照らし合わせるとWindows10 Proで手前に動かした時のみ値がおかしいように見えます。(負の値になるはずでは?)
    MSDNのドキュメントを確認してみましたが、そういった障害情報は見つけられず。

    本現象について、原因・解決策等何か情報をお持ちの方がいらっしゃいましたら、ご教授いただけないでしょうか。
    よろしくお願いいたします。


    • 編集済み r_naka 2017年5月31日 8:35
    2017年5月31日 8:33

回答

  • WM_MOUSEWHEELのWPARAMに入る値は32bit値です。m.WParam.ToInt32()で値を取得するようにして下さい。

    また、deltaは上記値を16ビット分右シフトすれば値を求められます。

    <追記>あ、ひょっとしたらToInt32()だと64bitプロセスでOverflowExceptionになるかも。(int)m.WParam.ToInt64()の方が安全かな。</追記>
    • 編集済み Hongliang 2017年5月31日 8:48
    • 回答としてマーク r_naka 2017年6月5日 2:18
    2017年5月31日 8:44
  • Message.WParamはIntPtr型であり、32bitアプリケーションでは32bit、64bitアプリケーションでは64bitになります。得られる値は同じですが、Int64型にキャストしたことにより結果が変化しています。

    WM_MOUSEWHELLはhigh-order wordつまり16~31bitの位置に値を保持していますから、これを得たいのであれば

    int wheelDelta = (Int32)m.WParam >> 16;

    とすべきです。

    2017年5月31日 8:47

すべての返信

  • WM_MOUSEWHEELのWPARAMに入る値は32bit値です。m.WParam.ToInt32()で値を取得するようにして下さい。

    また、deltaは上記値を16ビット分右シフトすれば値を求められます。

    <追記>あ、ひょっとしたらToInt32()だと64bitプロセスでOverflowExceptionになるかも。(int)m.WParam.ToInt64()の方が安全かな。</追記>
    • 編集済み Hongliang 2017年5月31日 8:48
    • 回答としてマーク r_naka 2017年6月5日 2:18
    2017年5月31日 8:44
  • 32ビット環境と64ビット環境の違いということはないですか?

    ※4287102976は32ビットで符号付とみなすと-7864320になります。


    • 編集済み なちゃ 2017年5月31日 8:51
    2017年5月31日 8:46
  • Message.WParamはIntPtr型であり、32bitアプリケーションでは32bit、64bitアプリケーションでは64bitになります。得られる値は同じですが、Int64型にキャストしたことにより結果が変化しています。

    WM_MOUSEWHELLはhigh-order wordつまり16~31bitの位置に値を保持していますから、これを得たいのであれば

    int wheelDelta = (Int32)m.WParam >> 16;

    とすべきです。

    2017年5月31日 8:47
  • Hongliang様

    ご回答ありがとうございます。

    環境はすべて64bitでしたので、Int64で変換するコードしか記載いませんでした。
    また、16ビットシフトするコードも試してはおりましたが、同様にWindous10 Pro環境の取得できる値がことなります。

    【実施コード(抜粋)】
    long wheelDelta = ((Int64)m.WParam >> 16);

    【取得結果】
    Windows7…wheelDeltaの値 -120
    Windows10 Enterprise…wheelDeltaの値 -120
    Windows10 Pro…wheelDeltaの値 65416

    皆様からご指摘ありました環境について、再度確認しましたが、やはり64bit環境に間違有りませんでした。

    2017年6月2日 0:38
  • なちゃ様

    ご回答ありがとうございます。

    改めて環境を確認いたしましたが、64bit環境に間違いはありませんでした。

    >※4287102976は32ビットで符号付とみなすと-7864320になります。
    64bit環境なのに、32bitの値が取れているということなのでしょうか…

    2017年6月2日 0:41
  • 佐祐理様

    ご回答ありがとうございます。

    ご教授いただいたソースコードとは少し異なりますが、
    Hongliang様への回答に記載しました通り、同じようなコードを既に実行しておりました。
    こちらの結果も同じようにWindows10 Proだけ異なったので、省いて質問していまいました…すみません。

    この取得されたデータがWindows10 Proのときに-120になると期待していたのですが…

    なちゃ様の回答より、32bitのときの値が取得されているように見えるのがすごく気になります。

    2017年6月2日 0:46
  • ええと。

    確かに64bitプロセスではIntPtrには64bit値が格納可能ですが、WM_MOUSEWHEELにおいてはそのうちの下位32bitしか使用しません。

    これは、上位32bitがゼロであるということは意味しません。何らかのゴミが混ざっている可能性もあります。

    なので、利用者はまずIntPtrの値を32bit値として扱う必要があります。64bit値のままでは上位32bitに何が入っているのか分からないからです。

    64bit値から下位32bit分だけを切り出すのには、私や佐祐理さんのコードにあるように、int(Int32)へのキャストが手軽です。0xFFFFFFFFとのbitwise ANDでもいいですけど。

    ちなみに、実際のところとしては、4287102976は16進数で0xff880000。このビット列をintとして扱うと0x80000000を超えているのでマイナスの値になり(2の補数)、そのまま右シフトするのでマイナスのまま-120になります。longとして扱うとプラスの値であり、そのまま右シフトしてもプラスのまま65416になります。
    • 編集済み Hongliang 2017年6月2日 1:09
    2017年6月2日 0:57
  • 前半はそのとおりだと思いますが、
    LONG値を0xffffffffとBitwiseAndだと、今回のように最終的にほしいものが「符号付きの16bit」という条件においては、
    符号拡張されないので-120のかわりに65416が取れる、という問題が残ると思います。

    jzkey

    2017年6月2日 1:06
  • 同じようなコードを既に実行しておりました。

    Hongliangさんも言及されていますが、「同じような」という認識が全く誤っていて、「全く異なるコード」を実行しています。

    # 3分間に3人に同じ指摘を受けているという状況を理解してください。

    2017年6月2日 1:23
  • なちゃ様

    ご回答ありがとうございます。

    改めて環境を確認いたしましたが、64bit環境に間違いはありませんでした。

    >※4287102976は32ビットで符号付とみなすと-7864320になります。
    64bit環境なのに、32bitの値が取れているということなのでしょうか…

    Hongliangさんの回答を見ていただくとわかると思いますが、有効な値は下位32ビット分なので、そもそも上位32ビットはどうなっているかわからないというのが問題でしたので、私の回答はちょっと外れていました。

    要するに64ビット値をそのまま使ってしまったので、環境によって、上位32ビットが1で埋められたか0で埋められたかなどによって動作が変わってしまっていた、ということです。


    • 編集済み なちゃ 2017年6月2日 1:24
    2017年6月2日 1:24
  • 皆様

    ご回答ありがとうございます。

    いろいろと理解がずれておりました。大変失礼しました。

    端的にいうと「WM_MOUSEWHEELのWPARAMに入る値は32bitなので、64bit値であるlongを使用してはいけない」という理解をしたのですがあってますでしょうか。

    上記踏まえたうえで「int wheelDelta = (int)(Int64)m.WParam >> 16;」を実行したところwindows10 Proでも他と同じ値を取得できました。
    ((Int64)m.WParamで取れてくる値がlong型だったので、intにする必要性がわかっていませんでした…)

    こちらのコードで他2環境と同じ値が取得できたので、ご報告をさせていただきます。

    2017年6月2日 5:08
  • 端的にいうと「WM_MOUSEWHEELのWPARAMに入る値は32bitなので、64bit値であるlongを使用してはいけない」という理解をしたのですがあってますでしょうか。

    そうではありません。

    int val = -7864320;
    Console.WriteLine("int: {0}, uint: {1}", val, (uint)val);
    

    このコードは理解できますでしょうか? 同じメモリパターンであっても型によって得られる値が違うということです。

    その上で、先に私から回答したように、仕様に照らし合わせると16bit~31bitの位置の値を読み取る必要があるということです。その上で、16bit~31bitの位置の値を読み取る手段としてどのようなC#コードを記述するかが問題となります。何名か回答されていますが、私が回答で提示した

    int wheelDelta = (Int32)m.WParam >> 16;

    が目的の処理を過不足なく表現しているため、このコードを推奨します。

    2017年6月2日 6:11
  • 佐祐理様

    1つ前の回答で言葉足りずだった部分があるので、少し補足させてください。
    ご提示いただいたコードを使用しなかった点については、windows10 Pro(64bit)で実行したところ、OverflowExceptionになりました。(他の環境では大丈夫でした)
    そのためHongliang様の追記で記載してもらった部分を参考に「int wheelDelta = (int)(Int64)m.WParam >> 16;」を使うことにいたいしました。
    決して推奨いただいたコードを検証していないということではないことを弁解させてください。

    また「longを使用してはいけない」についてですが
    (Int64)m.WParamで取れてくる値がlongでしたので、そのままの型で利用できると思っておりました。
    しかし「WM_MOUSEWHEELのWPARAMに入る値は32bit」ということなので、Int32で値を取得する必要がある(long→intする必要がある)
    …というように理解したのが、これが間違っているということでしょうか。

    >このコードは理解できますでしょうか? 同じメモリパターンであっても型によって得られる値が違うということです。
    ご提示いただいた例については理解はできます。
    今回の場合「マイナス値があるため符号なしのuintではなく、符号付きのintを使う」と思っているのですがこの認識にも間違いがありますでしょうか。

    よろしくお願いいたします。

    2017年6月5日 1:26
  • 説明のため順序を入れ替えます。

    今回の場合「マイナス値があるため符号なしのuintではなく、符号付きのintを使う」と思っているのですがこの認識にも間違いがありますでしょうか。

    そうではなくIntPtr構造体はInt32もしくはInt64への変換が用意されており、UInt32は用意されていません。ですので、Int32への直接変換を提案しました。

    ご提示いただいたコードを使用しなかった点については、windows10 Pro(64bit)で実行したところ、OverflowExceptionになりました。(他の環境では大丈夫でした)

    失礼しました。Visual C#では演算のオーバーフローおよびアンダーフローのチェックの既定値はチェックしないことになっています。ですがIntPtr.ToInt32()はこの設定を無視して強制的にチェックしていました。この点を確認していませんでした。

    # Windowsチームが32bit→64bitは符号なし拡張しておいて、.NETチームが64bit→32bitは符号ありでチェックするとか、理不尽すぎる…。というか開発チームの仲が悪すぎる。

    ということでしたら、「int wheelDelta = (int)(Int64)m.WParam >> 16;」としてください。

    2017年6月5日 1:55
  • 佐祐理様

    ご指摘ありがとうございます。

    今回のケースでintに変換する理由について、IntPtr構造体の仕様と理解いたしました。
    ご教授ありがとうございます。
    また、使用したコードについては、こちらのご説明が足りず不快な思いをさせてしまい申し訳ありません。

    また、オーバーフローのチェックもリンク先を確認させていただきました。

    2017年6月5日 2:15
  • 皆様

    お世話になりました。

    理解が足りなかったり、言葉足らずでご迷惑おかけいたしましたが
    何とか自分のなかで落とすことができました。

    本件につきまして、Hongliang様と佐祐理様の回答をマークさせていただきます。

    この度はありがとうございました。

    2017年6月5日 2:18