Событие COM UIA вызывается только после отмены регистрации

Я использую MS UIA (COM) через интерфейс C++/CLI, а мое приложение C# использует этот интерфейс C++/CLI (давайте назовем этот интерфейс / dll как uiacpp)

Я создал механизм обработки событий в uiacpp, в основном следуя примерам в https://msdn.microsoft.com/en-us/library/windows/desktop/ff625914(v=vs.85).aspx

Проблема, с которой я сталкиваюсь, заключается в том, что обработчик событий, который я регистрирую в UIA, вызывается только после того, как я отменяю регистрацию одного и того же события (то же самое каждый раз / разные события / типы событий и тесты). Когда я регистрирую событие, я вижу, что метод QueryInterface моего класса событий вызывается дважды, очевидно из UIA, поэтому UIA что-то с этим делает. Затем я запускаю событие в моем тесте, но ничего не происходит. И в тот момент, когда я отменяю регистрацию события, QueryInterface вызывается еще пару раз, затем вызывается обработчик события, а затем вызывается метод release для оставшихся ссылок (еще около 6 из них на данный момент), созданных UIA для очистки вещей.

Вот код:

Класс C++/CLI:

class CppUIAutomationEventHandler :
    public ::IUIAutomationEventHandler
{
private:
    LONG _refCount;

public:
    int _eventCount;
    gcroot<UIAMan::IUIAutomationEventHandler^> myHandler;
    static std::list<IUIAutomationEventHandler*> *EventRegister;

    // Constructor.
    CppUIAutomationEventHandler() : _refCount(1), _eventCount(0)
    {
    }

    // Constructor.
    CppUIAutomationEventHandler(
        UIAMan::IUIAutomationEventHandler^ aHandler)
        : _refCount(1)
        , _eventCount(0)
        , myHandler(aHandler)
    {
    }

    // IUnknown methods.
    ULONG STDMETHODCALLTYPE AddRef()
    {
        ULONG ret = InterlockedIncrement(&_refCount);
        return ret;
    }

    ULONG STDMETHODCALLTYPE Release()
    {
        ULONG ret = InterlockedDecrement(&_refCount);
        if (ret == 0)
        {
            delete this;
            return 0;
        }
        return ret;
    }

    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppInterface)
    {
        if (riid == __uuidof(IUnknown) || riid == __uuidof(IUIAutomationEventHandler))
            *ppInterface = static_cast<IUIAutomationEventHandler*>(this);
        else
        {
            *ppInterface = NULL;
            return E_NOINTERFACE;
        }
        this->AddRef();
        return S_OK;
    }

    // IUIAutomationEventHandler methods
    HRESULT STDMETHODCALLTYPE HandleAutomationEvent(::IUIAutomationElement * pSender, EVENTID eventID)
    {
        _eventCount++;

        myHandler->HandleAutomationEvent(gcnew CUIAutomationElement(pSender, false), eventID);

        return S_OK;
    }
};

и вот метод класса ref (managed C++), который C# вызывает для регистрации события (используя последний код в конце):

void CUIAutomation::AddAutomationEventHandler(
    int eventId
    , IUIAutomationElement^ element
    , TreeScope scope
    , IUIAutomationCacheRequest^ cacheRequest
    , IUIAutomationEventHandler^ handler)
{
    ::IUIAutomationElement* el = safe_cast<CUIAutomationElement^>(element)->getElement();
    ::IUIAutomationEventHandler* _handler = new CppUIAutomationEventHandler(handler);
    LastHResult = puia->AddAutomationEventHandler(
        eventId
        , el
        , (::TreeScope)(int)scope
        , (cacheRequest != nullptr) ? ((CUIAutomationCacheRequest^)cacheRequest)->getElement() : NULL
        , _handler);
    CppUIAutomationEventHandler::EventRegister->push_back(_handler);
};

Я использую список обработчиков, чтобы использовать их при отмене регистрации. Также puia - это COM-указатель, который был создан:

CUIAutomation::CUIAutomation()
{
    CoInitializeEx(NULL, COINIT_MULTITHREADED);

    ::IUIAutomation* _puia;
    HRESULT hr = CoCreateInstance(CLSID_CUIAutomation, NULL,
                    CLSCTX_INPROC_SERVER, IID_IUIAutomation,
                    (void**)&_puia);

    if (SUCCEEDED(hr))
        puia = _puia;
}

И, наконец, это вызовы C#:

Реализация класса автоматизации обработчика с использованием uiacpp:

    class AutomationHandler : IUIAutomationEventHandler
    {
        public AutomationHandler()
        {
        }

        public void HandleAutomationEvent(IUIAutomationElement sender, int eventId)
        {
            Console.WriteLine("IUIAutomationEventHandler called");
        }
    }

и C# зарегистрировать / отменить регистрацию строк:

var aHandler = new AutomationHandler();
uia.AddAutomationEventHandler(UIA_EventIds.UIA_Window_WindowOpenedEventId, uia.GetRootElement(), TreeScope.TreeScope_Subtree, null, aHandler);

// for debugging
bool loop = true;
while(loop)
{
    Thread.Sleep(500);
}

uia.RemoveAutomationEventHandler(UIA_EventIds.UIA_Window_WindowOpenedEventId, uia.GetRootElement(), aHandler);

1 ответ

Решение

Эти события COM отправляются через цикл сообщений Windows.

Это в сочетании с тем фактом, что вы не качаете сообщения между регистрацией и отменой регистрации, приводит к тому, что события откладываются до тех пор, пока вы не отмените регистрацию и не вернетесь в основной цикл обработки сообщений.

Одним из решений будет использование await Task.Delay вместо блокирующего сна.

Другие вопросы по тегам