回调和AccessibleObjectFromEvent调用似乎均按预期方式工作,并且我没有使用任何锁定机制,但是如果回调中存在AccessibleObjectFromEvent,则winforms窗体有时会锁定。
我注意到它冻结时在right-clicking on its taskbar icon unfreezes it处具有这个奇怪的属性。
使用调试器暂停只会将您带到Application.Run(new Form1())
,而.Net端似乎没有任何阻止-调试器的窗口更新甚至可以触发一些Active Accessibility事件,然后让您在回调中逐步处理这些事件-有效-在表格保持冻结状态的所有过程中!
我注意到AccessibleObjectFromEvent通过发送WM_GETOBJECT message来工作,但是.Net端永远不会停留在AccessibleObjectFromEvent调用上,而从SetWinEventHook回调内部调用AccessibleObjectFromEvent是AFAIK做主动可访问性的正常方法。>
当冻结时,我还没有注意到与Active Accessibility事件的任何关联,但是我确实没有足够的信息来排除这种情况。我还尝试过编译x86(而不是Any),并且没有区别。
我将其简化为最小形式:
using Accessibility;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace WindowsFormsApp1 {
static class Program {
// PInvoke declarations, see http://www.pinvoke.net/default.aspx/user32.setwineventhook
[DllImport("user32.dll")]
static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, WinEventFlag dwFlags);
public delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
[DllImport("oleacc.dll")]
public static extern uint AccessibleObjectFromEvent(IntPtr hwnd, uint dwObjectID, uint dwChildID, out IAccessible ppacc, [MarshalAs(UnmanagedType.Struct)] out object pvarChild);
[Flags]
public enum WinEventFlag : uint {
/// <summary>Events are ASYNC</summary>
Outofcontext = 0x0000,
/// <summary>Don't call back for events on installer's thread</summary>
Skipownthread = 0x0001,
/// <summary>Don't call back for events on installer's process</summary>
Skipownprocess = 0x0002,
/// <summary>Events are SYNC, this causes your dll to be injected into every process</summary>
Incontext = 0x0004
}
static IntPtr hookHandle = IntPtr.Zero;
static GCHandle garbageCollectorHandle;
static void AccessibilityEventHandler(IntPtr hWinEventHook, uint eventType, IntPtr hWnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) {
try {
object objChild = null;
IAccessible accWindow = null;
AccessibleObjectFromEvent(hWnd, (uint)idObject, (uint)idChild, out accWindow, out objChild);
Debug.WriteLine("Hook was invoked");
} catch (Exception ex) {
Debug.WriteLine("Exception " + ex);
}
}
[STAThread]
static void Main() {
WinEventDelegate callback = new WinEventDelegate(AccessibilityEventHandler);
// Use the member garbageCollectorHandle to keep the delegate object in memory. Might not be needed, and can't properly pin it because it's not a primitive type.
garbageCollectorHandle = GCHandle.Alloc(callback);
SetWinEventHook(
0x7546, // eventMin (0x7546 = PropertyChanged_IsOffscreen)
0x7546, // eventMax
IntPtr.Zero,
callback,
0,
0,
WinEventFlag.Outofcontext
);
// Two hooks are not necessary to cause a freeze, but with two it can happen much faster - sometimes within minutes
SetWinEventHook(
0x0001, // eventMin (0x0001 = System_Sound)
0x0001, // eventMax
IntPtr.Zero,
callback,
0,
0,
WinEventFlag.Outofcontext
);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}
使用这种最小的应用程序,很难注意到消息泵冻结,但是一旦锁定,您将无法拖动窗口或将其置于前台。该错误是间歇性的,通常在5分钟内发生,有时需要花费我很长时间才能放弃,如果您将其放置一整夜,并且该表单在早上仍然可以响应,则可能取决于计算机/操作系统(在Win 10.0.17174上进行过尝试)和10.0.17763,.net 4.5.2和4.6.1)。
我确定99%的用户需要冻结对AccessibleObjectFromEvent的调用才能冻结应用程序,但是间歇性冻结无法完全确定。
遵循Jimi的建议,我运行了带有标志的最小应用程序
WinEventFlag.Outofcontext | WinEventFlag.Skipownthread | WinEventFlag.Skipownprocess
花了一段时间才冻结,但仍冻结了。 Debug.WriteLine()
调用表明它仍在正常地响应Active Accessibility事件-即通过该回调没有发生递归繁忙循环(至少现在我正在查找),但是表单已冻结。它在任务管理器中使用0%的CPU。
冻结现在略有不同,因为任务管理器没有将其列为“无响应”,并且我可以通过左键单击任务栏图标将其置于前台-通常,任务栏甚至无法将其带来到前台。但是,表单仍然无法移动或调整大小,并且无法通过单击将其置于前台。
我还在AccessibleObjectFromEvent调用之前添加了一些Debug.WriteLine,并跟踪了重入深度,我可以看到它有时是可重入的,但通常在展开前只有一个深度,而在展开之前不超过13完全放松。这似乎是由于消息队列中已经有许多事件引起的,而不是由钩子处理程序递归地导致必须随后处理的事件引起的。该UI当前处于冻结状态,并且该钩子处理程序的深度为0(即当前未重入)。