Outlook COM C++插件OnSend事件无法触发问题求助
Outlook COM插件OnSend事件无法触发的解决方法
问题说明
通过ATL开发Outlook COM插件,尝试监听ApplicationEvents的OnSend事件以记录邮件发送日志,插件已成功加载且DispEventAdvise返回成功,但发送邮件时OnSend方法始终不执行。
核心问题点
- 事件签名不匹配:Outlook的
ApplicationEvents::OnSend实际参数为IDispatch* Item(邮件对象)和VARIANT_BOOL* Cancel(可修改的取消标志),原代码中OnSend仅接收VARIANT_BOOL Cancel,参数类型、数量均错误。 - 事件函数信息定义错误:
fiMailItemEvents的参数结构与OnSend实际参数不匹配。 - 未保存应用对象强引用:
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
相关产品推荐
相关产品推荐

