为什么在 IUserNotificationCallback COM 对象上查询 IMarshall 接口

Why is IMarshall Interface queried on IUserNotificationCallback COM object?

本文关键字:查询 IMarshall 接口 对象 IUserNotificationCallback COM 为什么      更新时间:2023-10-16

使用 IUserNotification2 from Microsoft

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

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

#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方法。

看到这里,我只是将调用更改为显示。

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接口的查询接口上被调用。采用经典的 COM 风格。但是我收到了对IID_IMarshall的查询,我通过添加

else if (inRiid == IID_IMarshal) {
    printf("interface queried is IMarshalln");

在查询接口方法中。

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

我这会产生显示方法的失败。

注意

我阅读了 notifu 的代码,看看它是否有帮助,但它与我的基本相同。

溶液

显然,罗曼·将调用更改为CoInitializeEx更改为if (!SUCCEEDED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED)))使其有效!

再次感谢

文档中没有任何地方提到我应该回答这个 IID

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

因此,您基本上应该将*ppvObject设置为 NULL 并在不实现接口时返回 E_NOINTERFACE,或者返回 S_OK 并使用有效的指针初始化*ppvObject,一旦调用者不再需要指针IUnknown::Release调用做好准备。

您的解决方案

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

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

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

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

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

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

执行涉及接口指针的单元间调用时,COM 首先查询IMarshal接口的对象:

COM 通过以下方式使用 IMarshal

的实现:当需要创建指向对象的远程接口指针时(即,当指向对象的指针作为远程函数调用中的参数传递时),COM 会查询对象的 IMarshal 接口。如果对象实现了它,COM 将使用 IMarshal 实现来创建代理对象。如果对象未实现 IMarshal,COM 将使用其默认实现。

所以,这确实是有据可查的。

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

  • 注意:QueryInterface的实现充满了微妙之处。 例如,在C++中,实现接口时,应this强制转换为适当的接口指针类型,如果打算使用聚合或撕除,则应AddRef实际的接口指针。

如果你没有实现自定义封送处理,我建议你在被要求IMarshal时不要做任何特别的事情,只需将其作为未实现的接口处理即可。

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