get_accChildCount returns 0 when it shouldn't

2019-06-05 05:54发布

问题:

I'm trying to enumerate tabs of IE from an extension and from a standalone application. For one of the MSAA nodes get_accChildCount returns 0 when called from extension, while it should return 1 according to inspect and a call from standalone application.

  • The problem was described previously at StackOverflow, yet it was solved via a hack that doesn't work for me. /clr and /MT are incompatible.
  • Also there was a topic on MSDN with the same issue. There's no single answer there.
  • If you run IE with administrator privileges, it works properly.
  • API Monitor shows several thousand calls in a minimal example, and it's unclear which of these are related. The minimal example is attached below.

What are the undocumented cases when get_accChildCount returns wrong child count?

What other method could I use to activate the tab by URL in most versions of IE?

#include <atlbase.h>
#include <atlcom.h>
#include <atlctl.h>
#include <atltypes.h>
#include <atlsafe.h>
#include <io.h>
#include <fcntl.h>
#include <windows.h>

#include <iostream>
#include <string>
#include <vector>
#include <boost/format.hpp>
#include <fstream>
using namespace std;

CComPtr<IAccessible> get_acc_by_hwnd(HWND hwnd) {
    CComPtr<IAccessible> ret;
    HRESULT hr = ::AccessibleObjectFromWindow(hwnd, OBJID_WINDOW, IID_IAccessible, (void**) &ret);
    if (FAILED(hr) || !ret) {
        wcout << L"Accessible::Accessible invalid hwnd" << endl;
    }
    return ret;
}

std::vector<CComPtr<IAccessible>> get_acc_children(CComPtr<IAccessible> acc) {
    std::vector<CComPtr<IAccessible>> ret;
    long count;
    if (FAILED(acc->get_accChildCount(&count))) return ret;
    long count_obtained = 0;
    if (!count) return ret;
    std::vector<CComVariant> accessors(count);
    if (FAILED(::AccessibleChildren(acc, 0, count, &*accessors.begin(), &count_obtained))) return ret;
    accessors.resize(count_obtained);
    for (auto vtChild : accessors) {
        if (vtChild.vt != VT_DISPATCH) continue;
        CComQIPtr<IAccessible> pChild = vtChild.pdispVal;
        if (pChild) ret.push_back(pChild);
    }
    return ret;
}

bool is_client(CComPtr<IAccessible> acc) {
    CComVariant var;
    HRESULT hr = acc->get_accRole(CComVariant(CHILDID_SELF), &var);
    return SUCCEEDED(hr) && var.vt == VT_I4 && var.lVal == 0xA;
}

std::wstring get_descr(CComPtr<IAccessible> acc) {
    CComBSTR str;
    HRESULT hr = acc->get_accDescription(CComVariant(CHILDID_SELF), &str);
    return SUCCEEDED(hr) && str ? std::wstring(str) : L"";
}

int main() {
    ::CoInitialize(nullptr);
    _setmode(_fileno(stdout), _O_U16TEXT);

    // put HWND of the window that contains tab labels
    // it's hardcoded to minimize quantity of API calls
    HWND hwnd = reinterpret_cast<HWND>(0x002D0696);
    CComPtr<IAccessible> iaccessible;
    HRESULT hr = ::AccessibleObjectFromWindow(hwnd, OBJID_WINDOW, IID_IAccessible, (void**) &iaccessible);
    if (FAILED(hr) || !iaccessible) {
        wcout << L"AccessibleBrowser::activate_tab " L"failed to get IAccessible for IE" << endl;
        return EXIT_FAILURE;
    }

    wstring const sentinel = L"\r\n";
    for (auto child : get_acc_children(iaccessible)) if (is_client(child)) {
        for (auto child1 : get_acc_children(child)) { // fails here in extension
            for (auto child2 : get_acc_children(child1)) {
                std::wstring descr = get_descr(child2);
                auto pos = descr.find(sentinel);
                if (pos == string::npos) continue;
                auto tab_url = descr.substr(pos + sentinel.size());
                wcout << tab_url << endl;
            }
        }
    }
}

回答1:

I poked at your program for a while without having much to show for it. Perhaps I realized too late that it was not supposed to reproduce the problem :( I can only provide some possibly helpful hints to get you to look under the right kind of rock.

yet it was solved via a hack that doesn't work for me

These programmers made a simple mistake, they forgot to call CoInitialize/Ex(). A very common oversight. Using the /clr build option works around that mistake because it is now the CLR that calls it. You can easily repro this mishap, just comment out the CoInitialize() call. Unfortunately it works for a while without any loud errors being produced, but you do get 0 for certain accobjects. You'll notice your program no longer finds the tabs.

Not so sure I can cleanly explain this, certain COM-style object models don't actually use the COM infrastructure. DirectX is the best example, we can add UIAutomation to that list. I assume it will silently fail like this when the client app's component is COM-based. Unclear if it is, DirectUIHWnd is quite undocumented.

So stop looking for that as a workaround, you did not forget to call CoInitialize() and it will be taken care of by IE before it activates your extension.

If you run IE with administrator privileges, it works properly.

That's a better rock. Many programmers run VS elevated all the time, the roles might be reversed in the case of UIA. Do keep in mind that your extension runs in a sandbox inside IE. No real idea how elevating IE affects this. One thing you cannot do from code running in the low-integrity sandbox is poke at UI components owned by a code that runs in a higher integrity mode. Google "disable IE enhanced protected mode" and follow the guideline to see what effect that has on your addin.