Click here to Skip to main content
15,867,686 members
Articles / Desktop Programming / MFC
Article

Implementing Outlook 2002/XP Event Sinks in MFC C++ 2003 .NET

Rate me:
Please Sign up or sign in to vote.
4.79/5 (13 votes)
29 May 20034 min read 153.9K   2.1K   43   36
How to implement Outlook event sinks to alert your application when new mail has been received.

Introduction

Recently it became necessary for me to create an application which was alerted whenever new mail was received using Microsoft Outlook 2002/XP.

Although there are already several articles on hooking into Outlook, I found that the automation implementation had changed considerably for Outlook 2002/XP and Microsoft Visual Studio .NET 2003. The second problem was that amongst the thousands of articles available, there are VERY few MFC or C++ ones. From my research I only managed to find a single MSDN article (KB 309301) relating to handling events using the .NET development environment. Unfortunately this was for Excel. A little bit of playing around and I successfully managed to convert this KB article to work with Outlook. Reading a bit about COM architecture also helped out on this new topic to me.

Building the application

Firstly, we need to create our default application:

  1. Start Visual Studio and select the 'File' menu, followed by 'New' and then select 'MFC application'. For its type, select 'Dialog Based'. Enter MFCOutlookEvent for its project name.
  2. Select 'Class View' and right click inside the window to select 'Add Class'. For your class type select 'MFC Class from TypeLib'. A type library is basically an interface to expose the objects of a COM component.
  3. Ensure 'Add class from Registry' option is selected, and select 'Microsoft Outlook 10.0 Object Library <9.1>' from the 'Available type libraries' drop down menu.
  4. A new list of interfaces should appear. For our purpose you only need to add the following:
    • _Application
    • _NameSpace
    • _Folders
    • _Items
    • _MailItem
    • MAPIFolder

    Clicking finish will make the code wizard generate the correct headers for each of the interfaces for you. _Application for example will become CApplication.h.

  5. Next we need to add a new generic class called CAppEventListener with IDispatch as its base class.
  6. Copy the following code into 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. Copy all of the following code into 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. Add the following to the top of your MFCOutlookEventsDlg.h header file.
    #include "AppEventListener.h"
  9. Add the following towards the end of it:
    private:
        CApplication m_OutlookApplication;
        CAppEventListener* m_pAppEventListener;
  10. Next, add two buttons to your dialog, calling them IDC_START and IDC_STOP. Add click handlers to each of the buttons. For the 'Start' button, add the following code:
    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 );
    

    For the 'Stop' button add the following code:

    if( m_pAppEventListener != NULL )
    {
         m_pAppEventListener->DetachFromSource();
         m_pAppEventListener->Release();
         m_pAppEventListener = NULL;
    }
    
    //m_OutlookApplication.Quit();
    m_OutlookApplication.ReleaseDispatch();
    
  11. Towards the top of your MFCOutlookEventsDlg.cpp file add the following:
    COleVariant covTrue((short)TRUE), covFalse((short)FALSE), 
                covOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR);
  12. Finally in order to initialize the use of the COM object, we must add the following code to your CMFCOutlookEventApp.InitInstance(void) function. Add it just above where you see AfxEnableControlContainer();:
    if(!AfxOleInit())
    {
        AfxMessageBox("Cannot initialize COM dll");
        return FALSE;
    }
    

Compile/run

You should now be able to compile/run your project. To test it, run it, and click 'Start'. It will automatically hook onto an existing instance of Outlook, or if no instance is found, it will start a new one. When new mail is received a trace entry will be placed into your 'Output' window of DevStudio saying 'HandleNewMail'.

Handling a new mail notification

You will probably wish to perform some form of operation whenever a new mail item is received. To do this simply navigate to your CAppEventListener.HandleNewMail(void) function and write whatever code you desire.

Detailed explanation

On closer look at the CAppEventListener class you will notice a few strange things. Firstly, in the header file there is the following code:

// Outlook AppEvents GUID
const IID IID_ApplicationEvents  = 
{0x0006304E,0x0000,0x0000,{0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}};

This is basically the unique GUID assigned to Outlook. To discover this, takes a bit of delving around. Load Visual Studio, click 'Tools' followed by 'OLE/COM Object Viewer'. From here, navigate down to expand your 'Type Libraries' section. You will probably have a huge list of available applications. Locate 'Microsoft Outlook 10.0 Object Library' and double click it. From here, locate 'coClass Application', expand this and highlight 'ApplicationEvents'. In the right hand view you should see the event GUID listed above.

You will also notice several application event functions. Above each function is a hexadecimal address. The NewMail() function which we were interested in should have the following:

[id(0x0000f003), helpcontext(0x0050df85)]
        void NewMail();

If you go back to Visual Studio and go to the CAppEventlistener.Invoke function, you will see a switch statement, and one of its cases is the exact same hexadecimal value (0x0000f003). This function is our main event handler if you so desire, and will call the appropriate function when the correct dispatch message arrives.

Not being an expert on COM, infact I only have about 2 hours experience so far, I can't really go into too much further detail, plus the code serves its purpose without knowing all the ins and outs! I hope this is of benefit to you all.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionGetting Global Address List attributes Pin
Kishore Shiranthadka29-Dec-08 17:13
Kishore Shiranthadka29-Dec-08 17:13 
Questionhow to get event of reply/compose button clicked Pin
_tasleem2-Jan-08 2:03
_tasleem2-Jan-08 2:03 
GeneralCan't launch Outlook! Pin
skinnyreptile16812-May-06 11:59
skinnyreptile16812-May-06 11:59 
GeneralRe: Can't launch Outlook! Pin
myliyan4-Apr-07 20:55
myliyan4-Apr-07 20:55 
GeneralRe: Can't launch Outlook! Pin
Hedi Sangoku24-Oct-13 1:45
Hedi Sangoku24-Oct-13 1:45 
GeneralOutlook After Send Event Pin
Rolando Sacramento27-Apr-06 22:01
Rolando Sacramento27-Apr-06 22:01 
GeneralDoing This in VB.net Pin
pftburger1-Feb-06 21:11
pftburger1-Feb-06 21:11 
Generalwhere to add the Get content of mail ?. Pin
rajesh_s763-Jan-06 18:44
rajesh_s763-Jan-06 18:44 
GeneralQuestion Pin
Tom Wright28-Mar-05 7:34
Tom Wright28-Mar-05 7:34 
GeneralOutlook Send Mail Event Pin
DotNetNuke6-Oct-04 9:44
DotNetNuke6-Oct-04 9:44 
GeneralRe: Outlook Send Mail Event Pin
Dennis Rivard14-Jan-05 2:56
Dennis Rivard14-Jan-05 2:56 
GeneralRe: Outlook Send Mail Event Pin
idm_j2325-Apr-06 4:32
idm_j2325-Apr-06 4:32 
GeneralRe: Outlook Send Mail Event Pin
Dennis Rivard25-Apr-06 4:51
Dennis Rivard25-Apr-06 4:51 
GeneralRe: Outlook Send Mail Event Pin
idm_j2325-Apr-06 5:03
idm_j2325-Apr-06 5:03 
GeneralRe: Outlook Send Mail Event Pin
idm_j2325-Apr-06 21:20
idm_j2325-Apr-06 21:20 
GeneralIs it posible to trap calendar events using this method Pin
UB4021-Jul-04 10:24
UB4021-Jul-04 10:24 
GeneralRe: Is it posible to trap calendar events using this method Pin
zebbedi22-Jul-04 0:35
zebbedi22-Jul-04 0:35 
GeneralRe: Is it posible to trap calendar events using this method Pin
lovedonny9-Nov-04 20:16
lovedonny9-Nov-04 20:16 
GeneralRe: Is it posible to trap calendar events using this method Pin
lovedonny9-Nov-04 21:54
lovedonny9-Nov-04 21:54 
GeneralRe: Is it posible to trap calendar events using this method Pin
sharat_6-Feb-06 23:27
sharat_6-Feb-06 23:27 
Questiondoes this work with Outlook2000 and Win2000? Pin
DawnYoshimura24-May-04 3:34
DawnYoshimura24-May-04 3:34 
AnswerRe: does this work with Outlook2000 and Win2000? Pin
zebbedi10-Jun-04 0:56
zebbedi10-Jun-04 0:56 
GeneralSyncObjectEvent Pin
panin20-Apr-04 2:40
panin20-Apr-04 2:40 
Generala way about getting the send mail event Pin
Justinas3-Feb-04 16:30
Justinas3-Feb-04 16:30 
GeneralRe: a way about getting the send mail event Pin
zebbedi10-Jun-04 0:53
zebbedi10-Jun-04 0:53 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.