在 MFC 应用程序中实现 IServiceProvider

Implementing IServiceProvider in an MFC application

我需要在开源MFC应用程序中实现IServiceProvider接口;特别是我的 TTSApp 申请。

我正在尝试添加对 IAccessibleApplication interface 的支持,屏幕阅读器使用它来获取有关应用程序名称和版本信息的信息。

似乎 Google Chrome 通过 AXPlatformNodeWin class 实现了 IServiceProvider 接口,它派生自 CComObjectRootEx class 和其他 classes和界面。问题是 MFC 应用程序不使用 CComObjectRootEx class;它被 ATL 使用。

我找到了 IServiceProviderImpl Class。不幸的是,我找不到任何关于它如何适应应用程序上下文的信息。我的 class 层次结构中的哪个 class 需要派生自 IServiceProviderImpl Class;我的 CWinApp 派生 class、我的 CDialogEx 派生 class 或其他一些 class?

在寻找这个问题的答案的过程中,我学到了很多东西。在任务中我掉进了兔子洞(Alice's Adventures in Wonderland by Charles Lutwidge Dodgson, a.k.a. Lewis Carroll) only to find Cthulhu (The Call of Cthulhu by H. P. Lovecraft)等着我。

我的初步研究使我找到了 afxwin.h 中定义的以下宏。

  • DECLARE_INTERFACE_MAP
  • BEGIN_INTERFACE_MAP
  • END_INTERFACE_MAP
  • BEGIN_INTERFACE_PART
  • END_INTERFACE_PART

我能找到的关于这些宏的最佳文档在 TN038: MFC/OLE IUnknown Implementation technical note. A good sample demonstrating the use of these macros and the implementation of the QueryService function is the TstCon 示例中。

当然,这又引出了另一个问题,window我为什么要这样做?为了回答这个问题,我查看了某个屏幕的源代码 reader 以了解它如何使用 IAccessibleApplication 接口。

以下函数虽然不是实际使用的代码,但演示了该技术(由于屏幕 reader 未开源,我无法共享实际代码)。

std::wstring GetApplicationNameUsingTheIAccessibleApplicationInterface(
    HWND hwnd, long idObject, long idChild)
{
    CComPtr<IAccessible> acc;
    CComVariant var;
    auto hr = AccessibleObjectFromEvent(hwnd, idObject, idChild, &acc, &var);
    if (hr != S_OK) return L"";
    if (!acc) return L"";
    CComQIPtr<IServiceProvider> serviceProvider = acc;
    if (!serviceProvider) return L"";
    CComQIPtr<IAccessibleApplication> application;
    hr = serviceProvider->QueryService(
        IID_IAccessible, __uuidof(IAccessibleApplication),
        reinterpret_cast<void**>(&application));
    if (FAILED(hr)) return L"";
    if (!application) return L"";
    CComBSTR appName;
    hr = application->get_appName(&text);
    if (FAILED(hr)) return L"";
    return appName.m_str;
}

此函数或类似函数是从我们的 WinEventProc callback function 调用的,以响应 EVENT_OBJECT_FOCUS 事件。这表明我需要为每个可以获得焦点的 window 执行此操作。

有了我认为是我的问题的答案,我投入并实现了 IAccessibleApplication 接口,并将必要的代码添加到我所有的可聚焦 windows。令我惊恐的是,我的 QueryService 函数从未被调用过。当我调试屏幕 reader 找出原因时,我发现下面这行代码隐含的 QueryInterface 失败了。

    CComQIPtr<IServiceProvider> serviceProvider = acc;

这导致了对 QueryInterface 调用失败原因的长期艰巨探索。

起初我正在做一个个人项目,所以我无法调用我雇主的资源。然后,完全是偶然的,我被分配了一项任务,要求我向需要信息的客户提供有关如何在 C++ 应用程序中实现 IAccessible2 接口的信息,以帮助他们使他们的应用程序更易于访问。万岁,终于可以请同事帮忙了!

我的同事指导我走上了正确的道路。

  1. 使用从 atlacc.h.
  2. 获得的源代码创建 IAccessibleProxyImpl class 和 CAccessibleProxy class 的自定义版本
  3. 为我的自定义 IAccessibleProxyImpl 添加一个 COM_INTERFACE_ENTRY for IAccessibleApplication in the COM_MAP (BEGIN_COM_MAP/END_COM_MAP) class。
  4. 使用 BEGIN_SERVICE_MAP, END_SERVICE_MAP, and SERVICE_ENTRY macros 提供 IServiceProvider 接口的实现。
  5. CWnd::CreateAccessibleProxy function 提供覆盖,使我的 windows 使用我的自定义可访问代理,从而实现我的 IAccessibleApplication 接口。

现在屏幕 reader 使用我为应用程序的 IAccessibleApplication 接口提供的应用程序名称。

我这样做的应用程序是开源的。这是我的 TTSApp application. I have also made an example that demonstrates how to use a similar technique to support the IAccessible2 interface available here.

我分享这个是希望这些信息对您有所帮助。