草庐IT

c++ - 为什么在 IUserNotificationCallback COM 对象上查询 IMarshall 接口(interface)?

coder 2024-02-22 原文

使用 Microsoft 的 IUserNotification2

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

我使用 Microsoft 的现有实现,请参见此处。 (请注意,我删除了标准 header 并进行了一些简化)。

#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。反对 Show 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 的QueryInterface 上被调用?界面。采用经典的 COM 风格。但是我收到了 IID_IMarshall 的查询,我通过添加检查了它

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

在查询接口(interface)方法中。

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

我这会导致显示方法失败。

注意

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

解决方案

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

再次感谢

最佳答案

nowhere in the documentation it is mentionned that I should answer to this IID

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

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

你的解决方案

Apparently the solution (suggested by a friend) is too always give back the this pointer even when returning E_NOINTERFACE.

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

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

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

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

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

关于c++ - 为什么在 IUserNotificationCallback COM 对象上查询 IMarshall 接口(interface)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28166791/

有关c++ - 为什么在 IUserNotificationCallback COM 对象上查询 IMarshall 接口(interface)?的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  3. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  4. ruby-on-rails - 按天对 Mongoid 对象进行分组 - 2

    在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev

  5. ruby - ECONNRESET (Whois::ConnectionError) - 尝试在 Ruby 中查询 Whois 时出错 - 2

    我正在用Ruby编写一个简单的程序来检查域列表是否被占用。基本上它循环遍历列表,并使用以下函数进行检查。require'rubygems'require'whois'defcheck_domain(domain)c=Whois::Client.newc.query("google.com").available?end程序不断出错(即使我在google.com中进行硬编码),并打印以下消息。鉴于该程序非常简单,我已经没有什么想法了-有什么建议吗?/Library/Ruby/Gems/1.8/gems/whois-2.0.2/lib/whois/server/adapters/base.

  6. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  7. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  8. ruby - 为什么 4.1%2 使用 Ruby 返回 0.0999999999999996?但是 4.2%2==0.2 - 2

    为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返

  9. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  10. ruby-on-rails - 如何验证非模型(甚至非对象)字段 - 2

    我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss

随机推荐