You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Outlook COM C++插件OnSend事件无法触发问题求助

Outlook COM插件OnSend事件无法触发的解决方法

问题说明

通过ATL开发Outlook COM插件,尝试监听ApplicationEvents的OnSend事件以记录邮件发送日志,插件已成功加载且DispEventAdvise返回成功,但发送邮件时OnSend方法始终不执行。

核心问题点

  1. 事件签名不匹配:Outlook的ApplicationEvents::OnSend实际参数为IDispatch* Item(邮件对象)和VARIANT_BOOL* Cancel(可修改的取消标志),原代码中OnSend仅接收VARIANT_BOOL Cancel,参数类型、数量均错误。
  2. 事件函数信息定义错误:fiMailItemEvents的参数结构与OnSend实际参数不匹配。
  3. 未保存应用对象强引用:OnConnection中未保留Outlook应用对象的强引用,可能导致事件接收器被提前释放。

修正步骤

1. 修正OnSend方法签名

将方法参数改为符合Outlook事件定义的类型:

STDMETHOD(OnSend)(IDispatch* pItem, VARIANT_BOOL* Cancel);

2. 修正事件函数信息结构

重新定义_ATL_FUNC_INFO,匹配OnSend的参数列表:

_ATL_FUNC_INFO fiOnSend = { CC_STDCALL, VT_EMPTY, 2, { VT_DISPATCH, VT_BOOL | VT_BYREF } };

3. 保存Outlook应用对象强引用

在类中使用CComPtr保存应用对象,避免被自动释放:

CComPtr<Outlook::_Application> m_spOutlookApp;

在OnConnection中初始化引用:

m_spOutlookApp = Application;

4. 替换硬编码DISPID(可选)

使用Outlook类型库中定义的dispidOnSend常量,替代硬编码的0x0000F005,提升代码兼容性:

SINK_ENTRY_INFO(1, __uuidof(Outlook::ApplicationEvents), dispidOnSend, OnSend, &fiOnSend)

修正后的完整代码

// Connect.h : Declaration of the CConnect

#pragma once    
#include "TestAddin2_i.h"
#include "resource.h"
#include <iostream>
#include <fstream>
#include <atlbase.h>
#include <atlcom.h>
#include <atlctl.h> 
#include <Ole2.h>
#import "C:\Program Files\Microsoft Office\root\Office16\MSOUTL.OLB" raw_interfaces_only, raw_native_types, named_guids, auto_rename

// 修正OnSend事件的函数信息结构
_ATL_FUNC_INFO fiOnSend = { CC_STDCALL, VT_EMPTY, 2, { VT_DISPATCH, VT_BOOL | VT_BYREF } };

#if defined(_WIN32_WCE) && !defined(_CE_DCOM) && !defined(_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA)
#error "Single-threaded COM objects are not properly supported on Windows CE platform, such as the Windows Mobile platforms that do not include full DCOM support. Define _CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA to force ATL to support creating single-thread COM object's and allow use of it's single-threaded COM object implementations. The threading model in your rgs file was set to 'Free' as that is the only threading model supported in non DCOM Windows CE platforms."
#endif

typedef IDispatchImpl<_IDTExtensibility2, &__uuidof(_IDTExtensibility2), &__uuidof(__AddInDesignerObjects), /* wMajor = */ 1> IDTImpl;

void LogMessage(const char* message) {
    std::ofstream file("C:/Users/Windows/Desktop/log.txt", std::ios_base::app);
    if (file.is_open()) {
        file << __TIME__ << " " << message << '\n';
        file.close();
    }
}

class ATL_NO_VTABLE CConnect :
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CConnect, &CLSID_Connect>,
    public IDispatchImpl<IConnect, &IID_IConnect, &LIBID_TestAddin2Lib, 1,0>,
    public IDispEventSimpleImpl<1, CConnect, &__uuidof(Outlook::ApplicationEvents)>,
    public IDTImpl
{
public:
    // 使用强引用保存Outlook应用对象
    CComPtr<Outlook::_Application> m_spOutlookApp;

    CConnect() {}

    STDMETHOD(Invoke)(DISPID dispidMember, const IID &riid, LCID lcid, WORD wFlags, DISPPARAMS *pdispparams, VARIANT *pvarResult, EXCEPINFO *pexceptinfo, UINT *puArgErr)
    {
        HRESULT hr=DISP_E_MEMBERNOTFOUND;
        if (DISP_E_MEMBERNOTFOUND == hr)
            hr = IDTImpl::Invoke(dispidMember, riid, lcid, wFlags, pdispparams, pvarResult, pexceptinfo, puArgErr);
        return hr;
    }

    DECLARE_REGISTRY_RESOURCEID(IDR_CONNECT)

    BEGIN_COM_MAP(CConnect)
        COM_INTERFACE_ENTRY(IConnect)   
        COM_INTERFACE_ENTRY(_IDTExtensibility2)
    END_COM_MAP()

    BEGIN_SINK_MAP(CConnect)
    // 使用类型库中的dispidOnSend替代硬编码值
    SINK_ENTRY_INFO(1, __uuidof(Outlook::ApplicationEvents), dispidOnSend, OnSend, &fiOnSend)
    END_SINK_MAP()

    DECLARE_PROTECT_FINAL_CONSTRUCT()

    HRESULT FinalConstruct() { return S_OK; }
    void FinalRelease() {}

    STDMETHOD(OnConnection)(LPDISPATCH Application, ext_ConnectMode ConnectMode, LPDISPATCH AddInInst, SAFEARRAY** custom)
    {
        LogMessage("Addin Loaded");
        if (Application)
        {
            // 保存应用对象强引用
            m_spOutlookApp = Application;
            LogMessage("Addin Loaded and DispEventAdvise");
            HRESULT hrAdvise = DispEventAdvise(m_spOutlookApp, &__uuidof(Outlook::ApplicationEvents));
            if (SUCCEEDED(hrAdvise)) {
                LogMessage("Registration of DispEventAdvice Successfull");
            }
            else {
                LogMessage("Failed to register DispEvent");
            }
        }
        return S_OK;
    }

    STDMETHOD(OnDisconnection)(ext_DisconnectMode RemoveMode, SAFEARRAY** custom)
    {
        if (m_spOutlookApp)
        {
            DispEventUnadvise(m_spOutlookApp, &__uuidof(Outlook::ApplicationEvents));
            m_spOutlookApp.Release();
        }
        return S_OK;
    }

    STDMETHOD(OnAddInsUpdate)(SAFEARRAY ** custom) { return S_OK; }
    STDMETHOD(OnStartupComplete)(SAFEARRAY ** custom) { return S_OK; }
    STDMETHOD(OnBeginShutdown)(SAFEARRAY ** custom) { return S_OK; }

    // 修正后的OnSend方法签名
    STDMETHOD(OnSend)(IDispatch* pItem, VARIANT_BOOL* Cancel) {
        LogMessage("Item Sent Successfully");
        // 可选:若需取消发送,设置*Cancel = VARIANT_TRUE;
        return S_OK;
    }
};

OBJECT_ENTRY_AUTO(__uuidof(Connect),CConnect)

额外注意事项

  • 确保Outlook类型库路径对应你的Office版本(Office16对应Office 2016/2019/365)。
  • 插件线程模型建议设置为Apartment,避免跨线程事件触发异常。
  • 在OnDisconnection中调用DispEventUnadvise释放事件注册,防止内存泄漏。

内容的提问来源于stack exchange,提问作者Acharya

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 00:52:04