Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

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

0.00/5 (No votes)
29 May 2003 1  
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