Как обратный вызов, так и вызов AccessibleObjectFromEvent работают должным образом, и я не использую никаких механизмов блокировки, но если в обратном вызове присутствует AccessibleObjectFromEvent, форма winforms иногда блокируется.
Я заметил, что когда он зависает, у него появляется это странное свойство, где щелчок правой кнопкой мыши на значке на панели задач приводит к его размораживанию .
Приостановка с отладчиком просто приводит к Application.Run(new Form1())
, и ничто не кажется заблокированным на стороне .Net - обновления окна отладчика могут даже вызвать некоторые события Active Accessibility, а затем позволить вам пройти через эти события в обратном вызове - работающем - все, пока форма остается замороженной!
Iобратите внимание, что AccessibleObjectFromEvent работает, отправляя сообщение WM_GETOBJECT , но сторона .Net никогда не застревает при вызове AccessibleObjectFromEvent, и вызов AccessibleObjectFromEvent из обратного вызова SetWinEventHook является AFAIK обычным способом сделать Active Accessibility.
Я не заметил какой-либо корреляции с событиями 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 необходим для зависания приложения, но с периодическими зависаниями нет никакого способа узнать абсолютноточно.
Reentrancy
Следуя советам Джими в комментариях, я запустил минимальное приложение с флагами
WinEventFlag.Outofcontext | WinEventFlag.Skipownthread | WinEventFlag.Skipownprocess
Потребовалось некоторое времязаморозить, но все еще заморожен.Вызовы Debug.WriteLine()
указывают, что он по-прежнему нормально реагирует на события Active Accessibility - т.е. рекурсивный цикл занятости не выполняется через этот обратный вызов (по крайней мере, не сейчас, когда я смотрю), но форма заморожена.Он использует 0% CPU в диспетчере задач.
Замораживание теперь немного отличается, так как диспетчер задач не отображает его как «не отвечающий», и я могу вывести его на передний план, щелкнув левой кнопкой мышизначок панели задач - обычно панель задач не может даже вывести ее на передний план.Однако форму по-прежнему нельзя переместить или изменить ее размер, и вы не можете вывести форму на передний план, щелкнув по ней.
Я также добавил несколько Debug.WriteLine перед вызовом AccessibleObjectFromEvent,и отслеживаем глубину повторного входа, я вижу, что он иногда повторяется, но обычно до глубины только один перед размоткой и не глубже 13 до полного разматывания.По-видимому, это вызвано тем, что в очереди сообщений уже есть много событий, а не обработчик ловушек, рекурсивно вызывающий события, которые он должен затем обработать.Пользовательский интерфейс в настоящее время заморожен, а обработчик перехватчика имеет глубину 0 (т. Е. В настоящее время не реентерабелен).