SendMessage 模拟右键崩溃目标应用
Posted
技术标签:
【中文标题】SendMessage 模拟右键崩溃目标应用【英文标题】:SendMessage to simulate right-click crashes the target app 【发布时间】:2011-09-24 12:34:02 【问题描述】:我正在编写一个 C# 自动化工具。
由于 Microsoft UI 自动化不提供任何模拟右键单击或弹出上下文菜单的方式,因此我使用 SendMessage
来代替。我宁愿不使用SendInput
,因为我不想抓住焦点。
但是,当我调用 SendMessage
时,它会使目标应用程序崩溃。
这是我的代码:
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
public void RightClick<T>(T element) where T: AutomationElementWrapper
const int MOUSEEVENTF_RIGHTDOWN = 0x0008; /* right button down */
const int MOUSEEVENTF_RIGHTUP = 0x0010; /* right button up */
var point = element.Element.GetClickablePoint();
var processId = element.Element.GetCurrentPropertyValue(AutomationElement.ProcessIdProperty);
var window = AutomationElement.RootElement.FindFirst(
TreeScope.Children,
new PropertyCondition(AutomationElement.ProcessIdProperty,
processId));
var handle = window.Current.NativeWindowHandle;
var x = point.X;
var y = point.Y;
var value = ((int)x)<<16 + (int)y;
SendMessage(new IntPtr(handle), MOUSEEVENTF_RIGHTDOWN, IntPtr.Zero, new IntPtr(value));
SendMessage(new IntPtr(handle), MOUSEEVENTF_RIGHTUP, IntPtr.Zero, new IntPtr(value));
知道我做错了什么吗?
【问题讨论】:
您能否再解释一下:“我宁愿不使用 SendInput,因为我不想获得焦点”? - 执行 SendInput 的应用程序不需要焦点。还是您不希望右键单击本身具有聚焦目标应用程序的副作用? (顺便说一句,UIAutomation 没有办法模拟右键单击的原因是它已经存在:SendInput!) UIAutomation 无法获取焦点。到目前为止,我已经设法在后台自动化我想要的一切。 SendInput 确实抓住了焦点,所以我想找到一个替代方案(SendMessage 没有)。此外,使用 user32.dll 绝不像 UIAutomation 那样干净或易于使用!有人可能会争辩说,SendInput 能够在任何地方点击和输入,那么我们为什么需要 UIAutomation? “有人可能会说 SendInput 能够在任何地方点击和输入,那么我们为什么需要 UIAutomation?” - Snark 回答:因为您不能使用 SendInput 在屏幕上查找元素。 UIAutomation 做了两类不同的事情:它让您发现有关 UI 的信息,并与它执行某些类型的交互。对于前者,它不需要关注 UI,因为它只是被动地观察。但是,如果您使用它与 UI 交互,它通常会聚焦目标 UI - 因为 UI 通常希望在与其交互时获得焦点。 SendInput 是最可靠的模拟输入方式:因为输入最终会通过与最终用户进行实际点击时完全相同的路径。当您使用 SendMessage/PostMessage 时,您最终可能会绕过一堆代码,因此不能保证它是完全等效的。它可能在某些或大多数情况下有效,但在其他情况下会默默或神秘地失败。例如,如果使用 SendInput 发送输入,鼠标钩子仍然可以工作,但如果使用 SendMessage 则不会被调用。 (SendMessage 还会绕过应用程序的 GetMessage 循环中的任何处理。) 是的,但它抓住了焦点,这是我明确试图避免的。 UI 自动化通常不关注目标 UI,而是在逻辑而不是物理级别上工作(例如,按下按钮,无论是鼠标单击还是回车键)。我现在正在与 Microsoft 的某个人联系,尝试为此提供解决方案。 【参考方案1】:你混淆了你的类型。您正在使用SendMessage
,它接受一个窗口消息(按照约定命名为WM_
...),但您传递给它的是一个MOUSEINPUT.dwFlags
值,该值用于SendInput
(命名为@987654325 @...)。你基本上是在胡言乱语。
您的代码实际上是在发送一个数值为 8 的窗口消息(在窗口消息中,这意味着 WM_KILLFOCUS
),然后是一个 0x10 == 16 (WM_CLOSE
) 的窗口消息。后者可能会导致您出现问题-您正在告诉窗口关闭。我不确定它为什么会崩溃,但它肯定会退出。
如果您使用SendMessage
,则需要向其传递窗口消息(WM_
,例如WM_RBUTTONDOWN
和WM_RBUTTONUP
)。
【讨论】:
那将是问题之一。您能否链接一些体面的文档,为我们这些从 Java 迁移到 C#、从未这样做过以及在其他地方关注大量误导性帖子的人解释它? 实际上,从头开始。这比我过去 24 小时一直在研究的任何事情都更能说明问题。 Microsoft 不知道如何编写比这更好的文档。谢谢! 对。它停止崩溃,这是我想要的第一件事......但现在上下文菜单还没有出现在桌面的后代中,即使我手动单击它也是如此。打扰!那好吧。回到绘图板......但我学到了很多东西。谢谢!【参考方案2】:这个回复足够长,我会把它放在一个答案槽中。我想我的回答基本上是你问的问题是基于一个不正确的假设。
关键问题是,虽然可以在您正在处理的同一桌面上在后台运行 API 类型的测试,但对基于 UI 的测试执行相同的测试很少能奏效。
对于长时间运行的 UI 测试,最好的选择是两台机器和一个键盘/监视器开关,或者使用终端服务在自己的会话中运行测试应用,这样它就可以拥有自己的世界观(焦点、鼠标、键盘状态)不会干扰您正在使用的桌面。
根本问题是某些 UI 资源(尤其是鼠标指针和键盘焦点)在桌面上的所有应用程序之间共享。许多(大多数?所有?)应用都假设当他们被交互时,他们可以随心所欲地使用这些。
您有时可以对应用程序“撒谎”并向其发送通常是输入最终结果的消息(例如发送 WM_LBUTTONDOWN 而不是发送输入),但如果应用程序最终查看全局鼠标状态,你最终会出现不一致。
例如,应用程序可能会使用作为参数传递的坐标来响应 WM_LBUTTONDOWN。或者它可能会忽略它们并改为调用 GetCursorPos - 如果鼠标真的在您的电子邮件程序而不是应用程序上,这可能会导致非常奇怪的行为。
或者你可以发送一个 WM_LBUTTONDOWN,应用程序通过调用一些辅助函数来响应它。辅助函数使用 GetKeyState(VK_LBUTTON) 来检查鼠标按钮是否真的按下 - 注意到它不是,所以提早退出。
(此外,发送最终结果消息会绕过应用程序可能依赖的其他内容;如果您将键直接发送到窗口,您将绕过通常在消息循环中的大部分加速器和对话框处理代码.)
如果应用程序使用 SetCapture() - 这对于可以点击的东西非常常见,例如按钮等 - 如果应用程序没有焦点,它将失败。你可能会很幸运,应用程序会忽略失败和运气——或者你可能不会。菜单类型的控件通常假设应用程序有焦点,如果他们注意到焦点实际上在其他地方,它们会自行关闭......
如果您拥有正在测试的应用程序,您可能可以考虑到这一点并编写它,以便可以在后台“测试”它:但请注意,它不再以与实际的用户交互 - 所以可以说它不是一个有效的用户等效测试用例! - 这取决于您的测试要求。
长话短说:在这种特定情况下,您可能可以在这里得到一些工作,但请注意这里潜伏着一大堆问题,请注意,这绝对不被视为 UI 测试最佳实践!
【讨论】:
对,所以我可以使用 SendInput(这确实有效) - 我只是在尝试 SendMessage。我特别想知道它为什么会崩溃,乔已经为我回答了这个问题。不过,感谢您提供更多信息。我要指出,至少有一个企业项目在我们的开发机器上使用该工具的早期版本已经有一段时间了,没有你所说的那种困难——因此我希望保持这种做事方式人们也想要一个 ContextMenu!【参考方案3】:你到底发送了什么消息,你能给出它们的 Windows 定义吗?根据 MSDN:
#define WM_RBUTTONDOWN 0x0204 #define WM_RBUTTONUP 0x0205【讨论】:
【参考方案4】:您没有发布崩溃的详细信息(异常类型?位置?),但我首先怀疑是重入问题。我会尝试使用 PostMessage 而不是 SendMessage。 SendMessage 是同步的,并在返回之前等待消息被处理,因此在其调用期间会执行一些事情。 PostMessage 只是将消息放入队列然后返回,然后进行处理。
【讨论】:
我想使用 SendMessage 而不是 PostMessage发布崩溃的详细信息,因为要花费大量精力/学习才能获得它们(发生在另一个过程中)和 *** = LazyWeb - 或者至少,它将是为下一个尝试相同事情的人准备的。谢谢!以上是关于SendMessage 模拟右键崩溃目标应用的主要内容,如果未能解决你的问题,请参考以下文章
SendMessage, postmessage模拟鼠标左键
如何诊断和解决 WCSession sendMessage(_:replyHandler:errorHandler:) 上的崩溃?