在 MFC 应用程序中实现 IServiceProvider

Posted

技术标签:

【中文标题】在 MFC 应用程序中实现 IServiceProvider【英文标题】:Implementing IServiceProvider in an MFC application 【发布时间】:2018-05-31 00:11:32 【问题描述】:

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

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

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

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

【问题讨论】:

我认为您正在寻找COM。 ATL 为 COM 提供 C++ 接口(类如CComObjectRootEx),而 MFC 为 WinAPI 提供 C++ 接口(@98​​7654326@ 或CDialog 如果您对这个问题的答案感兴趣,请继续关注。我将在接下来的几天内提供一个。在你等待的时候,我建议你阅读爱丽丝梦游仙境和克苏鲁的呼唤。这将让您了解我寻求问题答案的过程让我感觉如何。我掉进了兔子洞,却发现克苏鲁在等我。 【参考方案1】:

在寻找这个问题的答案的过程中,我学到了很多东西。在任务期间,我掉进了兔子洞(Alice's Adventures in Wonderland,Charles Lutwidge Dodgson,又名 Lewis Carroll),却发现 Cthulhu(H. P. Lovecraft 的The Call of Cthulhu)在等我。

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

DECLARE_INTERFACE_MAP BEGIN_INTERFACE_MAP END_INTERFACE_MAP BEGIN_INTERFACE_PART END_INTERFACE_PART

我能找到的关于这些宏的最佳文档在 TN038: MFC/OLE IUnknown Implementation 技术说明中。 TstCon 示例是一个很好的示例,用于演示这些宏的使用和 QueryService 函数的实现。

当然,这引出了另一个问题,我需要为哪个窗口执行此操作?为了回答这个问题,我查看了某个屏幕阅读器的源代码,以了解它如何使用 IAccessibleApplication 接口。

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

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 事件。这表明我需要为每个可以获得焦点的窗口执行此操作。

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

    CComQIPtr<IServiceProvider> serviceProvider = acc;

这导致了一个漫长而艰巨的探索,以找出对 QueryInterface 的调用失败的原因。

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

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

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

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

我这样做的应用程序是开源的。这是我的TTSApp 应用程序。我还做了一个示例,演示如何使用类似的技术来支持可用的 IAccessible2 接口here。

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

【讨论】:

以上是关于在 MFC 应用程序中实现 IServiceProvider的主要内容,如果未能解决你的问题,请参考以下文章

在 MFC 中实现多线程以更新外部函数可访问的内部字典

怎么在基于对话框的MFC程序中实现多线程?

怎么在基于对话框的MFC程序中实现多线程?

如何在 Win32 窗口中实现 MFC 资源?

如何在 mfc CListCtrl 中实现简单的复制/粘贴功能?

如何在 MFC CListCtrl 中实现“点击并按住”行为?