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:
- 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.
- 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.
- 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.
- 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.
- Next we need to add a new generic class called
CAppEventListener
with IDispatch
as its base class.
- 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"
const IID IID_ApplicationEvents =
{0x0006304E,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:
CAppEventListener();
~CAppEventListener();
STDMETHODIMP QueryInterface(REFIID riid, void ** ppvObj);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
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);
STDMETHODIMP HandleNewMail( );
STDMETHODIMP HandleStartup( );
STDMETHODIMP HandleQuit( );
STDMETHODIMP AttachToSource( IUnknown* pEventSource );
STDMETHODIMP DetachFromSource();
};
- Copy all of the following code into AppEventListener.cpp:
#include "stdafx.h"
#include "AppEventListener.h"
CAppEventListener::CAppEventListener() :
m_pConnectionPoint(NULL),
m_dwConnection(0)
{
m_refCount = 0;
}
CAppEventListener::~CAppEventListener()
{}
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;
}
STDMETHODIMP_(ULONG) CAppEventListener::AddRef()
{
return ++m_refCount;
}
STDMETHODIMP_(ULONG) CAppEventListener::Release()
{
m_refCount--;
if (m_refCount == 0)
{
delete this;
return 0;
}
return m_refCount;
}
STDMETHODIMP CAppEventListener::GetTypeInfoCount(UINT *iTInfo)
{
*iTInfo = 0;
return S_OK;
}
STDMETHODIMP CAppEventListener::GetTypeInfo(UINT iTInfo,
LCID lcid, ITypeInfo **ppTInfo)
{
return E_NOTIMPL;
}
STDMETHODIMP CAppEventListener::GetIDsOfNames(REFIID riid,
OLECHAR **rgszNames,
UINT cNames, LCID lcid,
DISPID *rgDispId)
{
return E_NOTIMPL;
}
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:
if(pDispParams->cArgs !=0)
return E_INVALIDARG;
else
{
HandleNewMail();
}
case 0x0000f006:
if(pDispParams->cArgs !=0)
return E_INVALIDARG;
else
{
HandleStartup();
}
case 0x0000f007:
if(pDispParams->cArgs !=0)
return E_INVALIDARG;
else
{
HandleQuit();
}
break;
}
return S_OK;
}
STDMETHODIMP CAppEventListener::HandleStartup()
{
OutputDebugString("HandleStartup\n");
HRESULT hr = S_OK;
return hr;
}
STDMETHODIMP CAppEventListener::HandleNewMail()
{
OutputDebugString("HandleNewMail\n");
HRESULT hr = S_OK;
return hr;
}
STDMETHODIMP CAppEventListener::HandleQuit()
{
OutputDebugString("HandleQuit\n");
HRESULT hr = S_OK;
return hr;
}
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;
}
STDMETHODIMP CAppEventListener::DetachFromSource()
{
HRESULT hr = S_OK;
if (m_pConnectionPoint != NULL){
m_pConnectionPoint->Unadvise( m_dwConnection );
m_pConnectionPoint = NULL;
}
return hr;
}
- Add the following to the top of your MFCOutlookEventsDlg.h header
file.
#include "AppEventListener.h"
- Add the following towards the end of it:
private:
CApplication m_OutlookApplication;
CAppEventListener* m_pAppEventListener;
- 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;
}
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.ReleaseDispatch();
- 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);
- 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:
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.