65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.79/5 (13投票s)

2003年5月30日

4分钟阅读

viewsIcon

159075

downloadIcon

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 架构的一些知识也对这个我不太熟悉的主题有所帮助。

构建应用程序

首先,我们需要创建我们的默认应用程序

  1. 启动 Visual Studio,选择“文件”菜单,然后选择“新建”,再选择“MFC 应用程序”。对于类型,选择“基于对话框”。将项目名称输入为 MFCOutlookEvent
  2. 选择“类视图”,然后在窗口中右键单击以选择“添加类”。对于类类型,选择“来自类型库的 MFC 类”。类型库基本上是暴露 COM 组件对象的接口。
  3. 确保选中“从注册表中添加类”选项,然后在“可用类型库”下拉菜单中选择“Microsoft Outlook 10.0 Object Library <9.1>”。
  4. 应该会出现一个新的接口列表。出于我们的目的,您只需要添加以下内容:
    • _Application
    • _NameSpace
    • _Folders
    • _Items
    • _MailItem
    • MAPIFolder

    单击“完成”后,代码向导将为您生成每个接口的正确头文件。例如,_Application 将变成 CApplication.h

  5. 接下来,我们需要添加一个名为 CAppEventListener 的新通用类,并将 IDispatch 作为其基类。
  6. 将以下代码复制到 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();
    };
  7. 将以下所有代码复制到 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;
    }
  8. 在您的 MFCOutlookEventsDlg.h 头文件的顶部添加以下内容。
    #include "AppEventListener.h"
  9. 在文件的末尾附近添加以下内容
    private:
        CApplication m_OutlookApplication;
        CAppEventListener* m_pAppEventListener;
  10. 接下来,在您的对话框中添加两个按钮,分别命名为 IDC_STARTIDC_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();
  11. 在您的 MFCOutlookEventsDlg.cpp 文件的顶部附近添加以下内容:
    COleVariant covTrue((short)TRUE), covFalse((short)FALSE), 
                covOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR);
  12. 最后,为了初始化 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 小时的经验,所以不能再详细说明了!另外,代码本身已经足够使用了,无需了解所有细节!希望这对大家有所帮助。

© . All rights reserved.