C#でWM_MOUSEMOVEメッセージを処理しようとしています。
IntPtrの一種であるlParamからX座標とY座標を取得する適切な方法は何ですか?
試してみてください:(
これは初期バージョンであることに注意してください。最終バージョンについては以下をお読みください)
IntPtr xy = value;
int x = unchecked((short)xy);
int y = unchecked((short)((uint)xy >> 16));
通常は必要ありません(unchecked
「デフォルト」のc#プロジェクトがチェックされていないため)
これらが使用されるマクロの定義であると考えてください。
#define LOWORD(l) ((WORD)(((DWORD_PTR)(l)) & 0xffff))
#define HIWORD(l) ((WORD)((((DWORD_PTR)(l)) >> 16) & 0xffff))
#define GET_X_LPARAM(lp) ((int)(short)LOWORD(lp))
#define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp))
どこWORD == ushort
で、DWORD == uint
。私はいくつかのushort->short変換をカットしています。
補遺:
1年半後、64ビットの.NETの「気まぐれ」を経験したので、Celessに同意します(ただし、互換性の理由から、Windowsメッセージの99%はまだ32ビットであるため、問題はないと思います。今はそれほど大きくはありません。将来のためのものであり、何かをしたい場合は正しく行う必要があるためです。)
私が違うものにする唯一のものはこれです:
IntPtr xy = value;
int x = unchecked((short)(long)xy);
int y = unchecked((short)((long)xy >> 16));
「4バイトまたは8バイトの長さ」のチェックを行う代わりにIntPtr
、最悪の場合(8バイトの長さ)を取り、にキャストxy
しlong
ます。運が良ければ、ダブルキャスト(to long
、to short
/ to uint
)はコンパイラーによって最適化されます(最終的に、ofへの明示的な変換int
はIntPtr
赤いニシンです...それを使用すると、危険にさらされます。将来。常にlong
変換を使用してから、直接/必要なものに再キャストして、将来のプログラマーに自分が何をしているかを知っていることを示す必要があります。
テスト例: http: //ideone.com/a4oGW2(残念ながら32ビットのみですが、64ビットマシンを使用している場合は同じコードをテストできます)
32ビットと64ビットの両方に対応:
Point GetPoint(IntPtr _xy)
{
uint xy = unchecked(IntPtr.Size == 8 ? (uint)_xy.ToInt64() : (uint)_xy.ToInt32());
int x = unchecked((short)xy);
int y = unchecked((short)(xy >> 16));
return new Point(x, y);
}
- また -
int GetIntUnchecked(IntPtr value)
{
return IntPtr.Size == 8 ? unchecked((int)value.ToInt64()) : value.ToInt32();
}
int Low16(IntPtr value)
{
return unchecked((short)GetIntUnchecked(value));
}
int High16(IntPtr value)
{
return unchecked((short)(((uint)GetIntUnchecked(value)) >> 16));
}
これらも機能します:
int Low16(IntPtr value)
{
return unchecked((short)(uint)value); // classic unchecked cast to uint
}
int High16(IntPtr value)
{
return unchecked((short)((uint)value >> 16));
}
- また -
int Low16(IntPtr value)
{
return unchecked((short)(long)value); // presumption about internals
} // is what framework lib uses
int High16(IntPtr value)
{
return unchecked((short)((long)value >> 16));
}
逆に行く
public static IntPtr GetLParam(Point point)
{
return (IntPtr)((point.Y << 16) | (point.X & 0xffff));
} // mask ~= unchecked((int)(short)x)
- また -
public static IntPtr MakeLParam(int low, int high)
{
return (IntPtr)((high << 16) | (low & 0xffff));
} // (IntPtr)x is same as 'new IntPtr(x)'
受け入れられた答えは、C定義の適切な翻訳です。生の「void*」だけを直接処理している場合は、ほとんど問題ありません。ただし、.Net 64ビット実行環境で「IntPtr」を使用する場合、「unchecked」は、変換オーバーフロー例外がIntPtr内からスローされるのを停止しません。チェックされていないブロックは、IntPtr関数および演算子内で発生する変換には影響しません。現在、受け入れられている回答は、「チェックなし」の使用は必要ないと述べています。ただし、より大きな型から負の値にキャストする場合は常にそうであるように、「チェックなし」の使用は絶対に必要です。
64ビットの場合、受け入れられた回答から:
var xy = new IntPtr(0x0FFFFFFFFFFFFFFF);
int x = unchecked((short)xy); // <-- throws
int y = unchecked((short)((uint)xy >> 16)); // gets lucky, 'uint' implicit 'long'
y = unchecked((short)((int)xy >> 16)); // <-- throws
xy = new IntPtr(0x00000000FFFF0000); // 0, -1
x = unchecked((short)xy); // <-- throws
y = unchecked((short)((uint)xy >> 16)); // still lucky
y = (short)((uint)xy >> 16); // <-- throws (short), no longer lucky
64ビットでは、DmitryGの外挿バージョンを使用します。
var ptr = new IntPtr(0x0FFFFFFFFFFFFFFF);
var xy = IntPtr.Size == 8 ? (int)ptr.ToInt64() : ptr.ToInt32(); // <-- throws (int)
int x = unchecked((short)xy); // fine, if gets this far
int y = unchecked((short)((uint)xy >> 16)); // fine, if gets this far
y = unchecked((short)(xy >> 16)); // also fine, if gets this far
ptr = new IntPtr(0x00000000FFFF0000); // 0, -1
xy = IntPtr.Size == 8 ? (int)ptr.ToInt64() : ptr.ToInt32(); // <-- throws (int)
パフォーマンスについて
return IntPtr.Size == 8 ? unchecked((int)value.ToInt64()) : value.ToInt32();
IntPtr.Sizeプロパティは、アセンブリ間でインライン化されている場合に使用できるコンパイル時リテラルとして定数を返します。したがって、JITはこのほとんどすべてを最適化することができます。また行うことができます:
return unchecked((int)value.ToInt64());
- また -
return unchecked((int)(long)value);
- また -
return unchecked((uint)value); // traditional
そして、これら3つすべてが、常にIntPtr.ToInt64()と同等のものを呼び出します。ToInt64()、および'operator long'もインライン化できますが、インライン化される可能性は低くなります。サイズ定数よりも32ビットバージョンの方がはるかに多くのコードです。私は、一番上の解決策がおそらくより対称的に正しいことを提出します。また、符号拡張アーティファクトに注意することも重要です。これは、(long)int_valのようなもので64ビットすべてを無防備に埋めますが、ここではかなり詳しく説明しましたが、32ビットのインライン化にさらに影響を与える可能性があります。
使用法
if (Low16(wParam) == NativeMethods.WM_CREATE)) { }
var x = Low16(lParam);
var point = GetPoint(lParam);
将来のトラバーラーのために以下に示す「安全な」IntPtrモックアップ。
WIN32定義を32ビットに設定せずにこれを実行して、64ビットのIntPtrの動作の確実なシミュレーションを取得します。
public struct IntPtrMock
{
#if WIN32
int m_value;
#else
long m_value;
#endif
int IntPtr_ToInt32() {
#if WIN32
return (int)m_value;
#else
long l = m_value;
return checked((int)l);
#endif
}
public static explicit operator int(IntPtrMock value) { //(short) resolves here
#if WIN32
return (int)value.m_value;
#else
long l = value.m_value;
return checked((int)l); // throws here if any high 32 bits
#endif // check forces sign stay signed
}
public static explicit operator long(IntPtrMock value) { //(uint) resolves here
#if WIN32
return (long)(int)value.m_value;
#else
return (long)value.m_value;
#endif
}
public int ToInt32() {
#if WIN32
return (int)value.m_value;
#else
long l = m_value;
return checked((int)l); // throws here if any high 32 bits
#endif // check forces sign stay signed
}
public long ToInt64() {
#if WIN32
return (long)(int)m_value;
#else
return (long)m_value;
#endif
}
public IntPtrMock(long value) {
#if WIN32
m_value = checked((int)value);
#else
m_value = value;
#endif
}
}
public static IntPtr MAKELPARAM(int low, int high)
{
return (IntPtr)((high << 16) | (low & 0xffff));
}
public Main()
{
var xy = new IntPtrMock(0x0FFFFFFFFFFFFFFF); // simulate 64-bit, overflow smaller
int x = unchecked((short)xy); // <-- throws
int y = unchecked((short)((uint)xy >> 16)); // got lucky, 'uint' implicit 'long'
y = unchecked((short)((int)xy >> 16)); // <-- throws
int xy2 = IntPtr.Size == 8 ? (int)xy.ToInt64() : xy.ToInt32(); // <-- throws
int xy3 = unchecked(IntPtr.Size == 8 ? (int)xy.ToInt64() : xy.ToInt32()); //ok
// proper 32-bit lParam, overflow signed
var xy4 = new IntPtrMock(0x00000000FFFFFFFF); // x = -1, y = -1
int x2 = unchecked((short)xy4); // <-- throws
int xy5 = IntPtr.Size == 8 ? (int)xy4.ToInt64() : xy4.ToInt32(); // <-- throws
var xy6 = new IntPtrMock(0x00000000FFFF0000); // x = 0, y = -1
int x3 = unchecked((short)xy6); // <-- throws
int xy7 = IntPtr.Size == 8 ? (int)xy6.ToInt64() : xy6.ToInt32(); // <-- throws
var xy8 = MAKELPARAM(-1, -1); // WinForms macro
int x4 = unchecked((short)xy8); // <-- throws
int xy9 = IntPtr.Size == 8 ? (int)xy8.ToInt64() : xy8.ToInt32(); // <-- throws
}
通常、低レベルのマウス処理には、次のヘルパーを使用しました(IntPtrのサイズはx86 / x64に依存するとも見なされます)。
//...
Point point = WinAPIHelper.GetPoint(msg.LParam);
//...
static class WinAPIHelper {
public static Point GetPoint(IntPtr lParam) {
return new Point(GetInt(lParam));
}
public static MouseButtons GetButtons(IntPtr wParam) {
MouseButtons buttons = MouseButtons.None;
int btns = GetInt(wParam);
if((btns & MK_LBUTTON) != 0) buttons |= MouseButtons.Left;
if((btns & MK_RBUTTON) != 0) buttons |= MouseButtons.Right;
return buttons;
}
static int GetInt(IntPtr ptr) {
return IntPtr.Size == 8 ? unchecked((int)ptr.ToInt64()) : ptr.ToInt32();
}
const int MK_LBUTTON = 1;
const int MK_RBUTTON = 2;
}