为什么在 IUserNotificationCallback COM 对象上查询 IMarshall 接口
Why is IMarshall Interface queried on IUserNotificationCallback COM object?
使用 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;
}
// ...
NULL
或nullptr
在这里不会导致失败。但是,您的代码在其他方面是危险的: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
子项来引用包含接口定义的类型库。
- Mongodb c++驱动程序:如何查询元素的数组
- 查询SQLite数据库中的日期
- 如何在ArangoDb AQL查询中指定数据库
- Qt SQLite没有查询或参数计数不匹配
- 如何使用c++在VS 2019上运行SQL查询
- 从返回的顶点缓冲区查询顶点结构
- 以非特权用户身份查询 NTFS 特殊文件的元数据?
- C/C++ - 查询平台相关的换行符(用于内存映射文件)
- 查询 NFS 上的提升进程间::file_lock
- Qt JSON – 从子项查询
- 在 c++ 中解决段树以外的范围查询的有效方法是什么?
- 无法从 Win10 中的 IDirectDraw7 查询 IDirect3D7
- 如何查询以确定我的 MacOS/X 应用程序是否处于应用程序午睡模式?
- 在子数组中查找多个查询的不同(唯一)值的数量
- DNS 查询格式标头结构中的小字节序问题
- QSql查询行受影响的结果
- C++库相关查询
- 优化使用 C++ 查询 SQLite DB 中超过 5000 万条数据记录的方式
- MongoDB 使用数组中的 OR 条件构建查询
- 为什么在 IUserNotificationCallback COM 对象上查询 IMarshall 接口