【问题标题】:Why is IMarshall Interface queried on IUserNotificationCallback COM object?为什么在 IUserNotificationCallback COM 对象上查询 IMarshall 接口?
【发布时间】:2015-01-27 09:14:59
【问题描述】:

使用 Microsoft 的 IUserNotification2

我正在使用IUserNotification2 向软件用户显示通知。

我使用 microsoft 的现有实现,请参见此处。 (注意,我删除了标准标题并简化了一点)。

#include <Shobjidl.h> //IUserNotification2 interface header

void NotifyUser(const std::wstring &title,
    const std::wstring &text){
if (!SUCCEEDED(CoInitializeEx(nullptr, COINIT_MULTITHREADED)))
    throw std::exception("could not init COM");

IUserNotification2 * handleNotification = nullptr;
auto result = CoCreateInstance(CLSID_UserNotification, 0, CLSCTX_ALL, IID_IUserNotification2, (void**)&handleNotification);
if (!SUCCEEDED(result) || !handleNotification) {
    throw std::exception("could not create CLSID_UserNotification");
}
DWORD notif_flags = NIIF_RESPECT_QUIET_TIME|NIIF_WARNING;
result = handleNotification->SetBalloonInfo(title.c_str(), text.c_str(), notif_flags);
if (!SUCCEEDED(result))
    throw std::exception("could not SetBalloonInfo of notification");
if (!SUCCEEDED(handleNotification->Show(nullptr, 5000,nullptr))
        throw std::exception("failed Show of notification");

在没有回调的情况下执行此操作(即显示对点击事件没有操作的消息)我没有问题,我的代码可以正常工作。

添加点击事件回调

为了添加这样的回调,要求将IUserNotificationCallback对象传递给IUserNotification2对象的Show方法。

请看这里,我只更改了对 Show 的调用。

Callback cbk;
if (!SUCCEEDED(handleNotification->Show(nullptr, 5000,& cbk)))
        throw std::exception("failed Show of notification");

Callback 类实现了IUserNotificationCallback。请参阅下面的类的实现。

class Callback : public IUserNotificationCallback {
public:
    virtual HRESULT STDMETHODCALLTYPE OnBalloonUserClick(POINT * pt) override {return S_OK;}
    virtual HRESULT STDMETHODCALLTYPE OnContextMenu(POINT * pt) override {}
    virtual HRESULT STDMETHODCALLTYPE OnLeftClick(POINT * pt) override {return S_OK;}

    /// Implementing IUnknown Interface
    virtual ULONG STDMETHODCALLTYPE AddRef()override { return 1; }
    virtual ULONG STDMETHODCALLTYPE Release() override { return 0; }
    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID inRiid, void ** outAddressOfObjectPointer)override {
        if (outAddressOfObjectPointer) {
            if (inRiid == IID_IUnknown || inRiid == IID_IUserNotificationCallback)
            {
                *outAddressOfObjectPointer = this;
                AddRef();
                return NOERROR;
            }
        }
        *outAddressOfObjectPointer = nullptr;
        return E_NOINTERFACE;
    }
};

现在的问题,

当我调用Show方法时,我的IUserNotificationCallback对象在IUnknown接口的QueryInterface上被调用。采用经典的 COM 风格。但是我收到了对 IID_IMarshall 的查询,我通过添加检查了该查询

else if (inRiid == IID_IMarshal) {
    printf("interface queried is IMarshall\n");

在查询接口方法中。

但是我为什么要收到这个,文档中没有提到我应该回答这个 IID。 在这种情况下,我让 E_NOINTERFACE 在 void** 参数中返回一个 nullptr。

这会导致显示方法失败。

注意

我看了notifu的代码,看看有没有用,不过和我的基本一样。

解决方案

显然Roman R. 提供了一个可行的解决方案。 将CoInitializeEx 的调用更改为if (!SUCCEEDED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED))) 即可!

再次感谢您

【问题讨论】:

  • 它正在尝试编组公寓 (codeguru.com/cpp/com-tech/activex/apts/article.php/c5529/…) 之间的接口,并且无法使用 typelib 编组器(无论出于何种原因)这样做。看到这个stackoverflow.com/q/1714926/57428我想说你最好的选择是让你的代码在不需要编组的公寓配置中运行。如果您提供有关如何在组件的注册脚本中指定线程模型的更多数据,那么有人可以就此提供进一步的建议。
  • 顺便说一句,你怎么称呼CoInitializeEx()
  • @sharptooth,代码在帖子里:CoInitializeEx(nullptr, COINIT_MULTITHREADED)
  • 是的,我现在明白了。如果您在注册表中查找CLSID_UserNotification,您会看到它的“线程模型”设置为“公寓”。您的代码要求 MTA。如果没有在此配置中编组,您的代码和 COM 服务器之间的调用将无法正常工作。一种选择是要求COINIT_APARTMENTTHREADED
  • 顺便说一句,每次调用该函数时,您的代码都会调用 CoInitializeEx() 吗?

标签: c++ winapi com notifications msdn


【解决方案1】:

文档中没有提到我应该回答这个 IID

任何 COM 对象都可能被查询以获取该对象未知的接口。这是正常的,更重要的是,COM对象必须正确响应,然后任何COM对象都必须实现IUnknown和这个方法。

所以你基本上应该将*ppvObject设置为NULL并返回E_NOINTERFACE如果你没有实现接口,否则返回S_OK并用一个有效的指针初始化*ppvObject,为IUnknown::Release做准备调用者不再需要指针时调用。

你的解决方案

显然解决方案(由朋友建议)即使在返回E_NOINTERFACE 时也总是返回 this 指针。

因此是不正确的。如果调用者提供了一个由智能指针管理的变量然后尝试释放它,有时也可能非常危险。这对于标准编组操作系统代码来说不太可能,但它更可能更愿意立即丢弃该值。

如果你这样做,你的实现将是正确的

*outAddressOfObjectPointer = NULL;
if (inRiid == IID_IUnknown)
{
        printf("interface required is IUnknown\n");
        *outAddressOfObjectPointer = this;
        AddRef();
        return NOERROR;
}
// ...

NULLnullptr 不会在此处导致失败。然而,您的代码在其他方面很危险:COM 初始化、Release 调用中返回零以及本地堆栈支持的回调类实例,该实例在超出范围时被销毁,但以后仍可能通过暴露的接口指针调用。

现在回到最初的问题,编组在这里做了什么?您正在初始化一个 MTA 线程,并且您正在那里创建单元线程 COM 对象CLSID_UserNotification。 COM 在侧 STA 线程中为您创建通知 API,然后尝试将您的回调传递到那里以匹配线程。它必须询问您的 COM 对象是否自己进行封送处理,或者它需要提供这个东西。您的对象必须说它不会自行编组并且它对此一无所知。这就是正在发生的事情。将其设为COINIT_APARTMENTTHREADED,您将看到不同的图片。

【讨论】:

  • @dzada 请阅读以上内容;还有为什么你还需要 MTA?
  • 感谢 CoInitializeEx 调用中的 COINIT_APARTMENTTHREADED 改变了这一切
【解决方案2】:

在进行涉及接口指针的公寓间调用时,COM first queries the object for the IMarshal interface

COM 以下列方式使用您的 IMarshal 实现: 当需要创建指向您的对象的远程接口指针时(即,当指向您的对象的指针作为参数传递给远程函数调用),COM 查询您的对象以获取 IMarshal 接口。如果您的对象实现它,COM 使用您的 IMarshal 实现来创建代理对象。如果您的对象没有实现 IMarshal,COM 将使用其默认实现。

所以,这确实是有记录的。

至于QueryInterface,就像你告知你的对象实现了IUnknown和其他有用的接口一样,当你没有实现接口时,你必须告知。这是通过清空传出接口指针(以避免其封送处理)并返回E_NOINTERFACE来完成的。

  • 注意:QueryInterface 的实现充满了微妙之处。例如,在 C++ 中,当你实现一个接口时,你应该将 this 转换为适当的接口指针类型,如果你打算使用聚合或分离,你应该 AddRef 实际的接口指针。李>

如果你不实现自定义编组,我建议你在请求IMarshal时不要做任何特别的事情,把它当作一个未实现的接口来处理。

COM 然后将在当前激活上下文中查找标准封送拆收器(请参阅程序集清单中的comClassclrClass),然后在注册表中(请参阅Interface 注册表项)。类型库封送处理是一种特定类型的标准封送处理,其中 ProxyStubClsid32 指的是类型库封送处理程序之一({00020420-0000-0000-C000-000000000046} aka PSDispatch for dispinterfaces, {00020424-0000-0000-C000 -000000000046} 又名 PSOAInterface for [oleautomation] interfaces),它需要一个 TypeLib 子键来引用包含接口定义的类型库。

【讨论】:

    猜你喜欢
    • 2012-01-14
    • 1970-01-01
    • 2011-06-27
    • 2012-07-13
    • 1970-01-01
    • 2014-03-07
    • 2012-04-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多