(为什么)Windows;“Calc.exe”;缺少WndProc?

(为什么)Windows;“Calc.exe”;缺少WndProc?,c,vb.net,windows,winapi,spy++,C,Vb.net,Windows,Winapi,Spy++,我正在摆弄wndprocs和WinSpy++,我偶然发现了一个关于calc.exe的奇怪东西。 它似乎缺少WndProc 这是我的屏幕截图:我制作的一个测试程序,WinSpy++窗口,显示N/a和罪魁祸首 也许这个工具有点过时,但经验证据证明没有WndProc 我不知道这是不是故意的(这会很奇怪),或者我是否遗漏了什么 参考代码如下: Function FindWindow(title As String) As IntPtr Return AutoIt.AutoItX.WinGetH

我正在摆弄wndprocs和WinSpy++,我偶然发现了一个关于calc.exe的奇怪东西。 它似乎缺少WndProc

这是我的屏幕截图:我制作的一个测试程序,WinSpy++窗口,显示N/a和罪魁祸首

也许这个工具有点过时,但经验证据证明没有WndProc

我不知道这是不是故意的(这会很奇怪),或者我是否遗漏了什么

参考代码如下:

Function FindWindow(title As String) As IntPtr
    Return AutoIt.AutoItX.WinGetHandle(title)
End Function

Function GetWindowProc(handle As IntPtr) As IntPtr
    Return GetWindowLong(handle, WindowLongFlags.GWL_WNDPROC)
End Function
简而言之(关于您的代码):
GetWindowLong()
失败,因为您试图读取目标进程地址空间中的地址

解释

GetWindowLong()
返回0时,表示存在MSDN中的错误

如果函数失败,则返回值为零。要获取扩展错误信息,请调用GetLastError

选中
Marshal.GetLastWin32Error()
,您可能会看到错误代码是
error\u ACCESS\u DENIED
(数值为0x5)

为什么??因为
GetWindowLong()
试图获取窗口过程的地址(或句柄)(不是在代码中,而是在目标进程中),理论上它甚至可能是默认的窗口过程,但我从未见过一个应用程序主窗口不处理至少几条消息)。您可以使用这个技巧(但我从未尝试过!)来查看窗口是否使用默认过程(您是否有地址),我不知道……应该有人尝试一下

现在想想什么是
WNDPROC

LRESULT (CALLBACK* WNDPROC) (HWND, UINT, WPARAM, LPARAM);
地址(在进程A中有效)在进程B中不可调用(在进程B中根本没有意义)。Windows DLL代码段是跨进程共享的(我想,我没有检查,但在安全性和性能之间的游戏中这是合理的)

此外,
CallWindowProc(NULL,…)
将理解
NULL
作为一个特殊值来调用该窗口类的窗口过程(在
HWND
owner上)。从MSDN:

…如果此值是通过调用GetWindowLong函数获得的…窗口或对话框过程的地址,或仅对CallWindowProc有意义的特殊内部值

Microsoft Spy++是如何做到的(也许WinSpy++没有做到)?没有WinSpy++源代码很难说。当然,这不像
GetWindowLong()
那么容易,正确的方法应该包括
CreateRemoteThread()
和从中执行
LoadLibrary()
,但Microsoft Spy++和WinSpy++的源代码都不可用(AFAIK)供进一步检查

更新

WinSpy++检查/调试与这个问题无关(你应该给开发人员发一张罚单,你的源代码可能会因为我上面解释的原因而失败,你应该总是检查错误代码),但我们可以从中寻找乐趣

InjectThread.c
中,我们看到它使用
WriteProcessMemory
+
CreateRemoteThread
然后
ReadProcessMemory
读回数据(未省略相关代码):

“常规”选项卡和“类”选项卡中的窗口过程不同(在“类”选项卡中正确显示值)。从
DisplayClassInfo.c

//window procedure
if(spy_WndProc == 0)    
{
    wsprintf(ach, _T("N/A"));
}
else                    
{
    wsprintf(ach, szHexFmt, spy_WndProc);
    if(spy_WndProc != spy_WndClassEx.lpfnWndProc)
        lstrcat(ach, _T(" (Subclassed)"));
}

//class window procedure
if(spy_WndClassEx.lpfnWndProc == 0)
    wsprintf(ach, _T("N/A"));
else
    wsprintf(ach, szHexFmt, spy_WndClassEx.lpfnWndProc);
GetClassInfoEx(0, spy_szClassName, &spy_WndClassEx);
GetRemoteWindowInfo(hwnd, &spy_WndClassEx, &spy_WndProc, spy_szPassword, 200);
正如您所看到的,它们是不同的值(以不同的方式获得)。要填充的代码位于WinSpy.cGetRemoteWindowInfo.c中。从
WinSpy.c
中的
GetRemoteInfo()
提取代码:

//window procedure
if(spy_WndProc == 0)    
{
    wsprintf(ach, _T("N/A"));
}
else                    
{
    wsprintf(ach, szHexFmt, spy_WndProc);
    if(spy_WndProc != spy_WndClassEx.lpfnWndProc)
        lstrcat(ach, _T(" (Subclassed)"));
}

//class window procedure
if(spy_WndClassEx.lpfnWndProc == 0)
    wsprintf(ach, _T("N/A"));
else
    wsprintf(ach, szHexFmt, spy_WndClassEx.lpfnWndProc);
GetClassInfoEx(0, spy_szClassName, &spy_WndClassEx);
GetRemoteWindowInfo(hwnd, &spy_WndClassEx, &spy_WndProc, spy_szPassword, 200);
现在在
GetRemoteWindowInfo()
中,我们看到了对
GetClassInfoExProc
的调用(在另一个进程中注入):


如您所见(请使用源代码进行操作)
wcOutput
是在“类”选项卡中显示的内容,而
wndproc
是在“常规”选项卡中显示的内容。简单地
GetWindowLong()
失败,但
getClassInfo
没有(但它们不一定检索相同的值,因为(如果我没有错的话)您在
WNDCLASSEX
中拥有的是您在
RegisterClassEx
中注册的内容,但通过
GetWindowLong()获得的内容
是您使用的
SetWindowLong()

您是对的。它没有WndProc(…)函数。它只是简单地使用DlgProc来处理对话框事件。我现在用C/C++编写了“服务器/瘦客户端”代码,以捕获对诸如WndProc(…)之类的windows API函数的直接调用.任何Windows GUI功能都可以-开始绘制(…)例如,我使用CALC.EXE作为测试,可执行文件在服务器上运行,而GUI调用被中继/返回到瘦客户机/从瘦客户机返回。我只通过Vista测试了CALC.EXE版本。新版本有可能“编程”不同,这意味着不使用Win32 SDK。但是,即使MFC也只是Win32 SDK的外壳,

哪个WinSpy v你正在使用的版本?它看起来更新得很快,可能(但我猜!!!)不懂Unicode WndProc(错误地认为不懂)。我查看了Catch22网站,我使用的是最新发布的版本,即V1.7(约中的1.7.1…)。另外,如果有帮助的话,我使用的是Windows 7 x64。1.7.1?我刚刚检查了VS2k12,它的Spy++版本是11.0。我在这里检查:我使用的是WinSpy++,而不是Microsoft Spy++。您犯了一个简单的错误,请运行64位版本的Spy++。这样它就可以显示来自64位进程的信息,如Calc.exe。您可以在Common7\Tools\spyxx\U amd64.exe中找到它"这是有道理的,只是在我发布我的答案之前,我确实验证了Microsoft Spy++能够为其他程序的多个窗口获取窗口过程,但不能获取calc.exe的窗口过程。@hvd这是一种奇怪的行为,但是如果没有WinSpy++源代码,很难说WinSpy++bug在哪里。当然calc.exe不只是依赖于d默认窗口过程(打开第一个实例:地址A,再次打开第二个实例:地址A,关闭所有实例并重试,您将获得另一个地址)