Вызов AccessibleObjectFromEvent внутри обратного вызова SetWinEventHook вызывает взаимные блокировки - PullRequest
0 голосов
/ 04 января 2019

Как обратный вызов, так и вызов 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 (т. Е. В настоящее время не реентерабелен).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...