从非托管 C++ 代码访问 WPF
Accessing WPF From Unmanaged C++ Code
背景
我目前正在尝试开发一些测试软件,需要在桌面上通知 window 创建和激活事件,响应这些并遍历小部件树,以便阅读那些 window 上的所有可用文本=95=]。这需要跨 32 位和 64 位工作,理想情况下是从 XP 开始(叹气),但肯定是 7 向上。
到目前为止我做了什么
我已经为 WH_CBT
消息注册了一个有效的 SetWindowsHookEx()
样式系统挂钩,我专门处理 HCBT_ACTIVATE
和 HCBT_CREATEWND
事件。挂钩是在 DLL 中完成的,并且有一个挂钩程序控制它。该程序可用,并且 运行 适用于 32 位和 64 位(因为 HCBT
消息似乎不会被路由回不同体系结构(32/64 位)的挂钩进程,这与键盘消息不同).该软件全部使用非托管C++代码编写,即不依赖于CLI,程序是一个控制台应用程序。
当我收到上述两个事件中的任何一个时,我会使用给定的 HWND
句柄调用 EnumChildWindows()
,并在每个 window 上调用 GetWindowText()
。结果目前根据 PID 写到单独的日志文件中。
这一切都按预期工作,但仅此而已...
问题
如果我 运行 向上说 MSVS,我可以看到顶层内容,甚至可以看到“你确定要这样做吗”简单警告对话框的文本内容,但对于选项对话框,我得到的只是确定和取消 window 小部件和一些未命名的 windows.
阅读这篇文章,我相信原因是我看到了从老式 Win32 API 小部件到 WPF 控件小部件的转变。
Assumptions/Requirements
- 这是由于 Win32 到 WPF 的转换。
- 作为从 DLL 注入每个进程的
SetWindowsEx()
样式挂钩,这必须是非托管代码(即,您不能愉快地在某个随机进程中启动 CLI 并使用托管代码)。
- WPF 库示例全部使用托管代码。
- 据观察,某些应用程序不使用可访问性 API 等挂钩,我对此打了折扣。
- 我需要尽可能地减少依赖性,因为被测系统必须尽可能具有代表性(我知道,我到处都在注入代码,但这无济于事)。如果可以的话,我不想安装额外的大型 运行 次等。
- 在测试期间,UAC 将以默认级别打开。
问题
- 是否有任何方法可以从 DLL 中的非托管 C++ 代码遍历 WPF 小部件层次结构?想必一定有某种 native/unmanaged API 我可以调用?
- 如果做不到这一点,是否可以将
HWND
句柄转发给另一个进程,比如通过 WM_COPYDATA
和一个隐藏的 window,这实际上是一个托管进程,然后可以遍历小部件树代表注入的 DLL,访问 Win32 小部件,在需要时切换到 WPF?我的研究似乎表明这是不可能的...
- 进程是否有权在打开 UAC 的情况下执行 (2)?
最后我要说的是,我是一名 Unix/Linux GUI/systems/kernel 软件工程师,如果以上内容看起来很基础,但我找不到这个问题的答案,请提前致歉用例。这也是我的第一个 MS-Windows 程序!
非常感谢,
约翰.
如果您只需要收到有关 window 创建和激活事件的通知 - 最好的方法是使用
SetWinEventHook(EVENT_OBJECT_CREATE, EVENT_OBJECT_DESTROY, 0, WinEventProc, 0, 0, WINEVENT_OUTOFCONTEXT);
然后在 WinEventProc 中查找 idObject == OBJID_WINDOW && idChild == CHILDID_SELF
这绝对不依赖于 WPF,甚至可以从低完整性进程中工作。另外 - 您不需要在此处调用 EnumChildWindows,因为所有 windows 的通知工作。不需要单独的 dll 32/64 位,不需要注入,
例如
VOID CALLBACK WinEventProc( HWINEVENTHOOK /*hWinEventHook*/, DWORD event,
HWND hwnd, LONG idObject, LONG idChild, DWORD /*dwEventThread*/,
DWORD /*dwmsEventTime*/)
{
if (event == EVENT_OBJECT_CREATE)
{
if (idObject == OBJID_WINDOW && idChild == CHILDID_SELF)//
{
WCHAR sz[256], cn[256];
if (!GetWindowText(hwnd, sz, RTL_NUMBER_OF(sz)))
{
*sz = 0;
}
if (!GetClassNameW(hwnd, cn, RTL_NUMBER_OF(cn)))
{
cn[0]='?', cn[1]=0;
}
DbgPrint("%p <%S>::<%S>\n", hwnd, cn, sz);
}
}
}
对于 MSVS 下一个输出的选项对话框:
0000000000040900 <#32770>::<>
00000000000408FC <tooltips_class32>::<>
00000000000408FE <SysTreeView32>::<>
00000000000408FA <Button>::<Show &all settings>
00000000000408F8 <Button>::<OK>
00000000000408F6 <Button>::<Cancel>
00000000000408F4 <Button>::<&Reset>
00000000000508DC <Button>::<Apply>
00000000000508DA <SysTabControl32>::<>
000000000003090A <#32770>::<>
000000000003090C <Button>::<&Reuse current document window, if saved>
000000000003090E <Button>::<&Detect when file is changed outside the environment>
0000000000030910 <Button>::<Auto-&load changes, if saved>
0000000000030912 <Button>::<Allo&w editing of read-only files; warn when attempt to save>
0000000000030914 <Button>::<&Open file using directory of currently active document>
0000000000030916 <Button>::<Chec&k for consistent line endings on load>
00000000001F08AA <Button>::<Displa&y warning when global undo will modify edited files>
0000000000260336 <Button>::<&Show Miscellaneous files in Solution Explorer>
00000000001D08A6 <Static>::<items saved in the &Miscellaneous files project>
00000000001208A4 <Edit>::<>
00000000001408AE <Button>::<Save documents as &Unicode when data cannot be saved in codepage>
背景
我目前正在尝试开发一些测试软件,需要在桌面上通知 window 创建和激活事件,响应这些并遍历小部件树,以便阅读那些 window 上的所有可用文本=95=]。这需要跨 32 位和 64 位工作,理想情况下是从 XP 开始(叹气),但肯定是 7 向上。
到目前为止我做了什么
我已经为 WH_CBT
消息注册了一个有效的 SetWindowsHookEx()
样式系统挂钩,我专门处理 HCBT_ACTIVATE
和 HCBT_CREATEWND
事件。挂钩是在 DLL 中完成的,并且有一个挂钩程序控制它。该程序可用,并且 运行 适用于 32 位和 64 位(因为 HCBT
消息似乎不会被路由回不同体系结构(32/64 位)的挂钩进程,这与键盘消息不同).该软件全部使用非托管C++代码编写,即不依赖于CLI,程序是一个控制台应用程序。
当我收到上述两个事件中的任何一个时,我会使用给定的 HWND
句柄调用 EnumChildWindows()
,并在每个 window 上调用 GetWindowText()
。结果目前根据 PID 写到单独的日志文件中。
这一切都按预期工作,但仅此而已...
问题
如果我 运行 向上说 MSVS,我可以看到顶层内容,甚至可以看到“你确定要这样做吗”简单警告对话框的文本内容,但对于选项对话框,我得到的只是确定和取消 window 小部件和一些未命名的 windows.
阅读这篇文章,我相信原因是我看到了从老式 Win32 API 小部件到 WPF 控件小部件的转变。
Assumptions/Requirements
- 这是由于 Win32 到 WPF 的转换。
- 作为从 DLL 注入每个进程的
SetWindowsEx()
样式挂钩,这必须是非托管代码(即,您不能愉快地在某个随机进程中启动 CLI 并使用托管代码)。 - WPF 库示例全部使用托管代码。
- 据观察,某些应用程序不使用可访问性 API 等挂钩,我对此打了折扣。
- 我需要尽可能地减少依赖性,因为被测系统必须尽可能具有代表性(我知道,我到处都在注入代码,但这无济于事)。如果可以的话,我不想安装额外的大型 运行 次等。
- 在测试期间,UAC 将以默认级别打开。
问题
- 是否有任何方法可以从 DLL 中的非托管 C++ 代码遍历 WPF 小部件层次结构?想必一定有某种 native/unmanaged API 我可以调用?
- 如果做不到这一点,是否可以将
HWND
句柄转发给另一个进程,比如通过WM_COPYDATA
和一个隐藏的 window,这实际上是一个托管进程,然后可以遍历小部件树代表注入的 DLL,访问 Win32 小部件,在需要时切换到 WPF?我的研究似乎表明这是不可能的... - 进程是否有权在打开 UAC 的情况下执行 (2)?
最后我要说的是,我是一名 Unix/Linux GUI/systems/kernel 软件工程师,如果以上内容看起来很基础,但我找不到这个问题的答案,请提前致歉用例。这也是我的第一个 MS-Windows 程序!
非常感谢,
约翰.
如果您只需要收到有关 window 创建和激活事件的通知 - 最好的方法是使用
SetWinEventHook(EVENT_OBJECT_CREATE, EVENT_OBJECT_DESTROY, 0, WinEventProc, 0, 0, WINEVENT_OUTOFCONTEXT);
然后在 WinEventProc 中查找 idObject == OBJID_WINDOW && idChild == CHILDID_SELF 这绝对不依赖于 WPF,甚至可以从低完整性进程中工作。另外 - 您不需要在此处调用 EnumChildWindows,因为所有 windows 的通知工作。不需要单独的 dll 32/64 位,不需要注入, 例如
VOID CALLBACK WinEventProc( HWINEVENTHOOK /*hWinEventHook*/, DWORD event,
HWND hwnd, LONG idObject, LONG idChild, DWORD /*dwEventThread*/,
DWORD /*dwmsEventTime*/)
{
if (event == EVENT_OBJECT_CREATE)
{
if (idObject == OBJID_WINDOW && idChild == CHILDID_SELF)//
{
WCHAR sz[256], cn[256];
if (!GetWindowText(hwnd, sz, RTL_NUMBER_OF(sz)))
{
*sz = 0;
}
if (!GetClassNameW(hwnd, cn, RTL_NUMBER_OF(cn)))
{
cn[0]='?', cn[1]=0;
}
DbgPrint("%p <%S>::<%S>\n", hwnd, cn, sz);
}
}
}
对于 MSVS 下一个输出的选项对话框:
0000000000040900 <#32770>::<>
00000000000408FC <tooltips_class32>::<>
00000000000408FE <SysTreeView32>::<>
00000000000408FA <Button>::<Show &all settings>
00000000000408F8 <Button>::<OK>
00000000000408F6 <Button>::<Cancel>
00000000000408F4 <Button>::<&Reset>
00000000000508DC <Button>::<Apply>
00000000000508DA <SysTabControl32>::<>
000000000003090A <#32770>::<>
000000000003090C <Button>::<&Reuse current document window, if saved>
000000000003090E <Button>::<&Detect when file is changed outside the environment>
0000000000030910 <Button>::<Auto-&load changes, if saved>
0000000000030912 <Button>::<Allo&w editing of read-only files; warn when attempt to save>
0000000000030914 <Button>::<&Open file using directory of currently active document>
0000000000030916 <Button>::<Chec&k for consistent line endings on load>
00000000001F08AA <Button>::<Displa&y warning when global undo will modify edited files>
0000000000260336 <Button>::<&Show Miscellaneous files in Solution Explorer>
00000000001D08A6 <Static>::<items saved in the &Miscellaneous files project>
00000000001208A4 <Edit>::<>
00000000001408AE <Button>::<Save documents as &Unicode when data cannot be saved in codepage>