在 MFC C++ 2003 .NET 中实现 Outlook 2002/XP 事件接收器






4.79/5 (13投票s)
2003年5月30日
4分钟阅读

159075

2114
如何实现 Outlook 事件接收器,以便在收到新邮件时通知您的应用程序。
引言
最近,我需要创建一个应用程序,该应用程序在通过 Microsoft Outlook 2002/XP 收到新邮件时能够得到通知。
尽管已经有几篇关于集成 Outlook 的文章,但我发现对于 Outlook 2002/XP 和 Microsoft Visual Studio .NET 2003 而言,其自动化实现已发生重大变化。第二个问题是,在可用的数千篇文章中,很少有关于 MFC 或 C++ 的。根据我的研究,我只找到一篇 MSDN 文章(KB 309301)与使用 .NET 开发环境处理事件有关。不幸的是,这篇文章是关于 Excel 的。经过一些试验,我成功地将这篇 KB 文章移植到了 Outlook。阅读有关 COM 架构的一些知识也对这个我不太熟悉的主题有所帮助。
构建应用程序
首先,我们需要创建我们的默认应用程序
- 启动 Visual Studio,选择“文件”菜单,然后选择“新建”,再选择“MFC 应用程序”。对于类型,选择“基于对话框”。将项目名称输入为 MFCOutlookEvent。
- 选择“类视图”,然后在窗口中右键单击以选择“添加类”。对于类类型,选择“来自类型库的 MFC 类”。类型库基本上是暴露 COM 组件对象的接口。
- 确保选中“从注册表中添加类”选项,然后在“可用类型库”下拉菜单中选择“Microsoft Outlook 10.0 Object Library <9.1>”。
- 应该会出现一个新的接口列表。出于我们的目的,您只需要添加以下内容:
_Application
_NameSpace
_Folders
_Items
_MailItem
MAPIFolder
单击“完成”后,代码向导将为您生成每个接口的正确头文件。例如,
_Application
将变成 CApplication.h。 - 接下来,我们需要添加一个名为
CAppEventListener
的新通用类,并将IDispatch
作为其基类。 - 将以下代码复制到 AppEventListener.h 中
#pragma once #include "oaidl.h" #include "CApplication.h" #include "CNameSpace.h" #include "CFolders.h" #include "CMAPIFolder.h" #include "CItems.h" #include "CMailItem.h" //00024413-0000-0000-C000-000000000046 - Excel //0006304E-0000-0000-C000-000000000046 - Outlook // Outlook AppEvents GUID const IID IID_ApplicationEvents = {0x0006304E,0x0000,0x0000,{0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}}; // Excel AppEvents GUID //const IID IID_ApplicationEvents = //{0x00024413,0x0000,0x0000,{0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}}; class CAppEventListener : public IDispatch { protected: int m_refCount; IConnectionPoint* m_pConnectionPoint; DWORD m_dwConnection; public: //Constructor. CAppEventListener(); //Destructor. ~CAppEventListener(); /***** IUnknown Methods *****/ STDMETHODIMP QueryInterface(REFIID riid, void ** ppvObj); STDMETHODIMP_(ULONG) AddRef(); STDMETHODIMP_(ULONG) Release(); /***** IDispatch Methods *****/ STDMETHODIMP GetTypeInfoCount(UINT *iTInfo); STDMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo); STDMETHODIMP GetIDsOfNames(REFIID riid, OLECHAR **rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId); STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr); /**** Click Handler *****/ STDMETHODIMP HandleNewMail( ); STDMETHODIMP HandleStartup( ); STDMETHODIMP HandleQuit( ); /**** Attach/Detach from event source *****/ STDMETHODIMP AttachToSource( IUnknown* pEventSource ); STDMETHODIMP DetachFromSource(); };
- 将以下所有代码复制到 AppEventListener.cpp 中
#include "stdafx.h" #include "AppEventListener.h" //Constructor. CAppEventListener::CAppEventListener() : m_pConnectionPoint(NULL), m_dwConnection(0) { m_refCount = 0; } //Destructor. CAppEventListener::~CAppEventListener() {} /***************************************************************** * IUnknown Interfaces -- All COM objects must implement, either * directly or indirectly, the IUnknown interface. *****************************************************************/ /*************************************************************** * QueryInterface -- Determines if this component supports the * requested interface, places a pointer to that interface * in ppvObj if it is * available, and returns S_OK. If not, sets ppvObj to NULL * and returns E_NOINTERFACE. ***************************************************************/ STDMETHODIMP CAppEventListener::QueryInterface(REFIID riid, void ** ppvObj) { if (riid == IID_IUnknown){ *ppvObj = static_cast<IUnknown*>(this); } else if (riid == IID_IDispatch){ *ppvObj = static_cast<IDispatch*>(this); } else if (riid == IID_ApplicationEvents){ *ppvObj = static_cast<IDispatch*>(this); } else{ *ppvObj = NULL; return E_NOINTERFACE; } static_cast<IUnknown*>(*ppvObj)->AddRef(); return S_OK; } /********************************************************* * AddRef() -- In order to allow an object to delete itself * when it is no longer needed, it is necessary to maintain * a count of all references to this object. When a new * reference is created, this function * increments the count. *********************************************************/ STDMETHODIMP_(ULONG) CAppEventListener::AddRef() { return ++m_refCount; } /***************************************************************** * Release() -- When a reference to this object is removed, this * function decrements the reference count. If the * reference count is 0, then * this function deletes this object and returns 0. *****************************************************************/ STDMETHODIMP_(ULONG) CAppEventListener::Release() { m_refCount--; if (m_refCount == 0) { delete this; return 0; } return m_refCount; } /************************************************************ * IDispatch Interface -- This interface allows this class * to be used as an automation server, allowing its functions * to be called by other COM objects. ************************************************************/ /********************************************************** * GetTypeInfoCount -- This function determines if the * class supports type * information interfaces or not. It places 1 in * iTInfo if the class supports * type information and 0 if it does not. **********************************************************/ STDMETHODIMP CAppEventListener::GetTypeInfoCount(UINT *iTInfo) { *iTInfo = 0; return S_OK; } /****************************************************** * GetTypeInfo -- Returns the type information for the * class. For classes * that do not support type information, * this function returns E_NOTIMPL; ******************************************************/ STDMETHODIMP CAppEventListener::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo) { return E_NOTIMPL; } /******************************************************* * GetIDsOfNames -- Takes an array of strings and * returns an array of DISPIDs * that correspond to the methods or properties indicated. * If the name is not * recognized, returns DISP_E_UNKNOWNNAME. *******************************************************/ STDMETHODIMP CAppEventListener::GetIDsOfNames(REFIID riid, OLECHAR **rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId) { return E_NOTIMPL; } /************************************************************** * Invoke -- Takes a dispid and uses it to call * another of this class's * methods. Returns S_OK if the call was successful. **************************************************************/ STDMETHODIMP CAppEventListener::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr) { CString szText; szText.Format( "DISP_ID: %x\n", dispIdMember ); OutputDebugString( szText ); switch(dispIdMember) { case 0x0000f003: // NewMail() if(pDispParams->cArgs !=0) return E_INVALIDARG; else { HandleNewMail(); } case 0x0000f006: // Startup() if(pDispParams->cArgs !=0) return E_INVALIDARG; else { HandleStartup(); } case 0x0000f007: // Quit() if(pDispParams->cArgs !=0) return E_INVALIDARG; else { HandleQuit(); } break; } return S_OK; } /******************************************************************* * HandleNewMail -- This method processes the NewMail event for the * application attached to this event handler. ********************************************************************/ STDMETHODIMP CAppEventListener::HandleStartup() { OutputDebugString("HandleStartup\n"); HRESULT hr = S_OK; return hr; } /******************************************************************* * HandleNewMail -- This method processes the NewMail event for the * application attached to this event handler. *******************************************************************/ STDMETHODIMP CAppEventListener::HandleNewMail() { OutputDebugString("HandleNewMail\n"); HRESULT hr = S_OK; return hr; } /******************************************************************* * HandleNewMail -- This method processes the NewMail event for the * application attached to this event handler. *******************************************************************/ STDMETHODIMP CAppEventListener::HandleQuit() { OutputDebugString("HandleQuit\n"); HRESULT hr = S_OK; return hr; } /**************************************************************** * AttachToSource -- This method attaches to an event source. ****************************************************************/ STDMETHODIMP CAppEventListener::AttachToSource ( IUnknown* pEventSource ) { HRESULT hr = S_OK; IConnectionPointContainer* pCPC = NULL; hr = pEventSource->QueryInterface( IID_IConnectionPointContainer, (void**)&pCPC ); if (SUCCEEDED(hr)){ hr = pCPC->FindConnectionPoint( IID_ApplicationEvents, &m_pConnectionPoint ); if (SUCCEEDED(hr)){ hr = m_pConnectionPoint->Advise( this, &m_dwConnection ); } pCPC->Release(); } return hr; } /***************************************************************** * DetachFromSource -- This method detaches from an event source. *****************************************************************/ STDMETHODIMP CAppEventListener::DetachFromSource() { HRESULT hr = S_OK; if (m_pConnectionPoint != NULL){ m_pConnectionPoint->Unadvise( m_dwConnection ); m_pConnectionPoint = NULL; } return hr; }
- 在您的 MFCOutlookEventsDlg.h 头文件的顶部添加以下内容。
#include "AppEventListener.h"
- 在文件的末尾附近添加以下内容
private: CApplication m_OutlookApplication; CAppEventListener* m_pAppEventListener;
- 接下来,在您的对话框中添加两个按钮,分别命名为
IDC_START
和IDC_STOP
。为每个按钮添加单击事件处理程序。对于“开始”按钮,添加以下代码:if( m_OutlookApplication.CreateDispatch ( "Outlook.Application" ) == 0 ) { AfxMessageBox( "Can't launch Outlook!" ); return; } //Add an event handler for the Application object. m_pAppEventListener = new CAppEventListener(); m_pAppEventListener->AddRef(); m_pAppEventListener->AttachToSource ( m_OutlookApplication.m_lpDispatch );
对于“停止”按钮,添加以下代码:
if( m_pAppEventListener != NULL ) { m_pAppEventListener->DetachFromSource(); m_pAppEventListener->Release(); m_pAppEventListener = NULL; } //m_OutlookApplication.Quit(); m_OutlookApplication.ReleaseDispatch();
- 在您的 MFCOutlookEventsDlg.cpp 文件的顶部附近添加以下内容:
COleVariant covTrue((short)TRUE), covFalse((short)FALSE), covOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR);
- 最后,为了初始化 COM 对象的使用,我们必须将以下代码添加到您的
CMFCOutlookEventApp.InitInstance(void)
函数中。将其添加到您看到AfxEnableControlContainer();
的上方。if(!AfxOleInit()) { AfxMessageBox("Cannot initialize COM dll"); return FALSE; }
编译/运行
您现在应该能够编译/运行您的项目。要测试它,请运行它,然后单击“开始”。它将自动挂接到 Outlook 的现有实例,或者如果找不到实例,它将启动一个新的实例。当收到新邮件时,DevStudio 的“输出”窗口将显示一条跟踪条目,内容为“HandleNewMail”。
处理新邮件通知
当收到新邮件项时,您可能希望执行某种操作。为此,只需导航到您的 CAppEventListener.HandleNewMail(void)
函数并编写您想要的任何代码。
详细解释
仔细查看 CAppEventListener
类,您会注意到一些奇怪的地方。首先,在头文件中,有以下代码:
// Outlook AppEvents GUID const IID IID_ApplicationEvents = {0x0006304E,0x0000,0x0000,{0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}};
这基本上是分配给 Outlook 的唯一 GUID。要找到这个 GUID,需要进行一些深入的研究。加载 Visual Studio,单击“工具”,然后单击“OLE/COM 对象查看器”。从这里,导航到展开“类型库”部分。您可能会看到一个非常大的可用应用程序列表。找到“Microsoft Outlook 10.0 Object Library”并双击它。从这里,找到“coClass Application”,展开它,然后突出显示“ApplicationEvents”。在右侧的视图中,您应该会看到上面列出的事件 GUID。
您还会注意到几个应用程序事件函数。每个函数上方都有一个十六进制地址。我们感兴趣的 NewMail()
函数应该具有以下内容:
[id(0x0000f003), helpcontext(0x0050df85)] void NewMail();
如果您回到 Visual Studio 并转到 CAppEventlistener.Invoke
函数,您会看到一个 switch 语句,其中一个 case 正是这个十六进制值 (0x0000f003)。如果您愿意,这个函数是我们的主要事件处理器,当正确的调度消息到达时,它将调用相应的函数。
我不是 COM 专家,事实上我只有大约 2 小时的经验,所以不能再详细说明了!另外,代码本身已经足够使用了,无需了解所有细节!希望这对大家有所帮助。