实战经验:SetWindowLongPtr在开发64位程序的使用方法

实战经验:SetWindowLongPtr在开发64位程序的使用方法

作者:BlogUpdater |  时间:2017-05-10 |  浏览:11336 |  评论已关闭 条评论

在开发某些应用时,需要替换掉原来系统默认的WndProc,使用我们自己定义的WndProc,在32位程序开发下,我们可以使用Win32 API SetWindowLong。以下是该函数的描述:

SetWindowLong function
Changes an attribute of the specified window. The function also sets the 32-bit (long) value at the specified offset into the extra window memory.
Note This function has been superseded by the SetWindowLongPtr function. To write code that is compatible with both 32-bit and 64-bit versions of Windows, use the SetWindowLongPtr function.

函数原型如下:

LONG WINAPI SetWindowLong(
  _In_ HWND hWnd,
  _In_ int  nIndex,
  _In_ LONG dwNewLong
);

可以传入参数GWL_WNDPROC这一参数修改窗口的WndProc。但这里也明确提醒了我们,在开发同时兼容32位和64位程序时,微软建议我们使用SetWindowLongPtr这一函数,并传入GWLP_WNDPROC这一参数。原因在于:这里修改的WndProc实际上是修改函数指针,在64位系统下,指针已经从32位扩展到了64位,SetWindowLong的函数原型的第三个参数是32位的指针,在64位下已经不足以表示新的函数指针,所以有了新的SetWindowLongPtr这一函数。

以下是SetWindowLongPtr的描述:

SetWindowLongPtr function
Changes an attribute of the specified window. The function also sets a value at the specified offset in the extra window memory.
Note To write code that is compatible with both 32-bit and 64-bit versions of Windows, use SetWindowLongPtr. When compiling for 32-bit Windows, SetWindowLongPtr is defined as a call to the SetWindowLong function.

函数原型如下:

LONG_PTR WINAPI SetWindowLongPtr(
  _In_ HWND     hWnd,
  _In_ int      nIndex,
  _In_ LONG_PTR dwNewLong
);

这里的第三个参数已经变成了LONG_PTR,即一个64位的地址。
另外,在编译x64版本时,在使用了SetWindowLong时,系统编译失败并提示如下错误:
error C2065: ‘GWL_WNDPROC’ : undeclared identifier
这是因为微软在头文件中显式取消了GWL_WNDPROC的定义,这里编译器直接给出错误提示,迫使开发者使用新版本的SetWindowLongPtr。

#define GWL_WNDPROC         (-4)
#define GWL_HINSTANCE       (-6)
#define GWL_HWNDPARENT      (-8)
#define GWL_STYLE           (-16)
#define GWL_EXSTYLE         (-20)
#define GWL_USERDATA        (-21)
#define GWL_ID              (-12)
#ifdef _WIN64
#undef GWL_WNDPROC
#undef GWL_HINSTANCE
#undef GWL_HWNDPARENT
#undef GWL_USERDATA
#endif /* _WIN64 */
#define GWLP_WNDPROC        (-4)
#define GWLP_HINSTANCE      (-6)
#define GWLP_HWNDPARENT     (-8)
#define GWLP_USERDATA       (-21)
#define GWLP_ID             (-12)

下面记录一个在系统开发过程中一个关于SetWindowLong(Ptr)使用的一个容易忽略的问题:
新建一个基于对话框的MFC程序,定义一个新的WndProc:

LRESULT CALLBACK CMyDialog::MyWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	return AfxWndProc(hWnd, msg, wParam, lParam);
}

这里为了方便演示,新的WndProc直接调用了默认的AfxWndProc。

下面替换默认WndProc:

void CMyDialog::OnBnClickedButton1()
{
	SetWindowLong(m_hWnd, GWL_WNDPROC, reinterpret_cast<LONG>(MyWndProc));
}

以上代码在32位配置下成功编译通过并运行正常,但是在64位下编译错误。需要改为如下的版本:

void CMyDialog::OnBnClickedButton1()
{
	SetWindowLongPtr(m_hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(MyWndProc));
}

以上代码可以在32/64位配置下编译成功,运行正常。新的WndProc成功截获了所有发往窗口的消息。

下面给出误用的代码片段:

void CMyDialog::OnBnClickedButton1()
{
	SetWindowLongPtr(m_hWnd, GWLP_WNDPROC, reinterpret_cast<LONG>(MyWndProc));
}

以上代码在做reinterpret_cast时,错误的使用了LONG这一目标类型,此操作直接将64位地址截断为32位,所以,以上代码在32位版本下运行正常,但是在64位版本下会出现各种未定义的行为。

所以,结论如下:
为了开发同时兼容32和64位,统一使用:SetWindowLongPtr。

标签:

评论已关闭。