|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionFirst of all, if you are thinking Outlook Express, just forget it. The only three ways of integrating with Outlook Express are (and this is just my guessing) by paying Microsoft a lot of money and by giving them a really good reason to why you want to do that, or by hooking in through the encryption-entry, or by creating a proper hook and hacking your way in there. I've done the hacking (due to missing funds) and it's not a pretty sight, it works but it's everything from clean. So, this is not for Outlook Express, this is only for Outlook from Office. Ever thought of extending Outlook to add that extra functionality that you so much wished for? Well, look no further. I will try to give you enough information so that you can do that. There are two ways to extend Outlook, one is pure ATL where the other is just about some COM-interfaces in a plain DLL. We could have discussion about what way to do it, and I'm sure there are people who prefer the ATL-version to the COM-version. Referring to it as COM-version is wrong, well, not really, it is COM, but just for the sake of it, I'll refer to it as the Exchange-way. So why Exchange when we're talking about Outlook? It started with the old Exchange 4.0 client and Microsoft exposed a way to hook in to their mail client, and since Outlook is using the same model as Exchange, that entry point to the client has stayed and we should be grateful for that because its been tested through out all these years and it's 99.9% bug free where as the ATL-version isn't really that well tested. I guess you've understood by now that this article isn't about the ATL-version,... Also, I need to notify you that parts of this article are copied from MSDN, but with minor changes to allow people to actually understand what's written. Microsoft have never been really good at explaining their more complicated API's, don't know why but that's the way it is... I've decided not to start out with code, but with some background information first, once that is covered, we will make a small addin that adds a button to a new toolbar. This addin will not cover the entire architecture, but it will not only give you an idea to what you can do with an addin, but more importantly, it will (hopefully) explain the basics so that you can continue on your own in less time than it took for me... BackgroundYou need to at least know what a COM-interface is, and no, I'm not talking about that Before I get going, when and if you want to debug your addin, select Outlook.exe to be the executable in Dev-studio. Extending Outlook - the basics...When you want to extend Outlook, what you do is basically create a DLL, export an entry-point, register the addin with Outlook by modifying the registry. But before I start talking about how that is done, let's go through the basics of how an addin is loaded, why and when it is released. An addin is loaded several times, every time in a different context. Now, Outlook is nice enough to pass you enough information so that you can figure out in what context you've been loaded and by simply identifying the context, you can also figure out what you can do and what you shouldn't do.
In the table above, you can see three bars (five bars in total), each bar represents a different context. Each context has a timeline where the length of the bar is the time that context is loaded. Before we look at this, its worth mention that Outlook keeps an array of addins, and that each addin has its own map of when it wants to be loaded. So, if we take them in the load-order.
So, how does Outlook know when to load your addin then? The best way to explain this is to look at how you register the addin in the first place. Extending Outlook - registering the addin...There are two ways of telling Outlook about your addin and that is either by using an ECF-file or through the registry. I won't describe the ECF-format because that is too long and not the purpose of this article, so open up your registry and browse to HKEY_LOCAL_MACHINE\Software\Microsoft\Exchange\Client\Extensions. What you see here is, a list of addin's. Each entry in this list is an addin and apart from the name, the entry also tells Outlook when it wants to be loaded, what interfaces the addin implements and some quite uninteresting information (see MSDN: ) Let's take an example: Exchange Scan - 4.0;C:\Program Files\Network Associates\VirusScan\scanemal.dll;1;11000000000000;1110000; So, what can we see from this value? Let's break down the value to smaller pieces and I'll try to explain what they mean.
Now the first three items are not very hard to understand, so we'll look into the two items that are interesting instead. First the context-map, the map that will tell Outlook when to load a given addin :
Next in line is the interface-map, this map tells Outlook what interfaces you implement. If you are unsure (don't know how you can be unsure to what interface you implement, but hey, who am I to judge...) you can always fill this map with 1's, Outlook will then query your base interface for all the interfaces it might need and it's up to you to give the right response to that. This map contains 9 interfaces that you decide if you want to implement (where you can map 7 of these 9) and there is 1 that is mandatory and therefore can't be put in this map.
When you add an addin to the registry, Outlook caches the majority of the settings so that you need to tell him to reload the cache. There are three ways of doing this and I usually end up doing the first and the last, that way I won't have any surprises. Maybe not very clean but it works for me.
Typical code for this would be: char szAppPath[MAX_PATH]={0}; SHGetSpecialFolderPath(NULL, szAppPath, CSIDL_LOCAL_APPDATA, FALSE); strcat(szAppPath, "\\Microsoft\\Outlook\\extend.dat"); Ok, so now you know how to register your addin and how the addin is loaded but you still don't know how Outlook loads your addin, so let's talk about that now. Your DLL needs to export one function, just one tiny function to have your addin up and running. So what does this function look like then? LPEXCHEXT CALLBACK ExchEntryPoint()
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
CMyAddin *pExt = new CMyAddin();
return pExt;
}
As you can see, it's a callback that returns an instance of your addin-class. Now that doesn't really look like COM, does it? Well, it is and it is done the hard way. Outlook will actually query your object for different interfaces (see above) later on. What more can you see in this code? Well, to begin with, its a DLL that uses MFC. Now this is totally up to you if you want to use MFC or not, the addin itself doesn't require MFC, it's just me using it because I find it a lot easier to use and a lot less hard work than using AYL (MFC is Buggy, yes, I know, but this is just my personal opinion so please don't flame me for that...) Extending Outlook - developing the core of your addin...Start up your Developer Studio now and create a new project, make it a Regular DLL using MFC and call it OutlookAddin. First of all, open OutlookAddin.cpp and create a function as follows: LPEXCHEXT CALLBACK ExchEntryPoint()
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
CMyAddin *pExt = new CMyAddin();
return pExt;
}
Now, open your def-file and add the exported function ExchEntryPoint @1 Not very hard, so now we have an entry-point to the addin. Now we need to add a class, a generic one called Start by changing the class so that it derives from class CMyAddin : public IExchExt { public: CMyAddin(); ~CMyAddin(); STDMETHODIMP_(ULONG) AddRef(); STDMETHODIMP_(ULONG) Release(); STDMETHODIMP QueryInterface(REFIID riid, void** ppvObj); STDMETHODIMP Install(IExchExtCallback *lpExchangeCallback, ULONG context, ULONG ulFlags); ULONG m_ulRefCount; // Basic COM stuff, needed to know when // it's safe to delete ourselves ULONG m_ulContext; // Member to store the current context in. }; It's worth mention that the functions Now that you have the core of an addin, implementing these functions shouldn't be very hard, so let's do it: (I've skipped everything except the STDMETHODIMP CMyAddin::Install(IExchExtCallback *lpExchangeCallback,
ULONG mcontext,
ULONG ulFlags)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
HRESULT hRet = S_FALSE;
try
{
m_ulContext = mcontext;
switch(m_ulContext)
{
case EECONTEXT_TASK:
{
hRet = S_OK;
}
break;
case EECONTEXT_SESSION:
{
hRet = S_OK;
}
break;
case EECONTEXT_VIEWER:
{
hRet = S_OK;
}
break;
case EECONTEXT_REMOTEVIEWER:
case EECONTEXT_SENDNOTEMESSAGE:
case EECONTEXT_SENDPOSTMESSAGE:
case EECONTEXT_SEARCHVIEWER:
case EECONTEXT_ADDRBOOK:
case EECONTEXT_READNOTEMESSAGE:
case EECONTEXT_READPOSTMESSAGE:
case EECONTEXT_READREPORTMESSAGE:
case EECONTEXT_SENDRESENDMESSAGE:
case EECONTEXT_PROPERTYSHEETS:
case EECONTEXT_ADVANCEDCRITERIA:
default:
{
hRet = S_FALSE;
}
break;
};
}
catch(...)
{
}
return hRet;
}
You will also need to create a new CPP-file, call it MapiDefines.cpp that looks like this. The reason for this being put in a CPP-file is that these lines should not be compiled more than once. #include "stdafx.h" #define INITGUID #define USES_IID_IExchExt #define USES_IID_IExchExtAdvancedCriteria #define USES_IID_IExchExtAttachedFileEvents #define USES_IID_IExchExtCommands #define USES_IID_IExchExtMessageEvents #define USES_IID_IExchExtPropertySheets #define USES_IID_IExchExtSessionEvents #define USES_IID_IExchExtUserEvents #define USES_IID_IMessage #define USES_IID_IMAPIContainer #define USES_IID_IMAPIFolder #define USES_IID_IMAPITable #define USES_IID_IMAPIAdviseSink #define USES_IID_IMsgStore #define USES_IID_IMAPIForm #define USES_IID_IPersistMessage #define USES_IID_IMAPIMessageSite #define USES_IID_IMAPIViewContext #define USES_IID_IMAPIViewAdviseSink #define USES_IID_IDistList #define USES_IID_IMailUser #include <mapix.h> You will also need to add the following includes to your stdafx.h (these files should have been installed when you installed Dev-Studio). #include <atlbase.h> #include <mapix.h> #include <mapiutil.h> #include <mapitags.h> #include <mapiform.h> #include <initguid.h> #include <mapiguid.h> #include <exchext.h> #include <exchform.h> #include <mapidefs.h> #include <mapispi.h> #include <imessage.h> #include <ocidl.h> #pragma comment(lib,"mapi32") #pragma comment(lib,"Rpcrt4") #pragma comment(lib,"Version") Ok, so now we have a basic extension that doesn't do anything except that it gets loaded into Outlook at startup and released when Outlook is shutdown. Now, compile the project and sort any errors or warnings out before you go on to the next part of this article. You will not find the includes if you are developing in Visual Studio .NET, you will need to find either the Platform SDK for MAPI or the includes from Visual Studio 6. Extending Outlook - adding some buttons to the addin...First, lets extend our class to add a toolbar and a button at startup. For that, we need to ask Outlook for an object called <name>. We will also need to import two DLL's that defines what the "Outlook Object Model"-objects (also referred to as OOM) look like. // If you have Outlook 2000 installed on your machine, then use the // following lines #import "mso9.dll" #import "msoutl9.olb" // If you have Outlook 2002 installed on your machine, then use the // following lines #import "mso.dll" #import "msoutl.olb" // If you have Outlook 2003 installed on your machine, then use the // following lines #import "mso11.dll" #import "msoutl11.olb" I have used the outlook 2000 version but it doesn't matter which version you import, (you don't have to have the version you are importing installed on your machine, it's enough to have these DLL's, but it's a lot easier to debug if you have Outlook installed...anyway, I've put the import line in my stdafx.h. I have not included these files in the ZIP-file, you will have to locate them from your Outlook-installation folder. Apart from Copyright issues, they are big (Msox.dll is about 5.3 Mb). One thing you have to remember is that if you are using the functionality that only exists in Outlook 2003, then obviously, that won't work in Outlook 2000, but you will still be able to run your addin as long as you don't execute that piece of code. Also, forget about this whole thing if you intend to run under Outlook 97 and Outlook 98. OOM existed back then but wasn't fully implemented. You will though be able to create a toolbar under Outlook 98 but it's not straight forward and there are no events for the buttons (among other things), so you will need to do some hacking (hook's and subclassing) if you want to detect when a button is clicked. If you still want to be able to run this under Outlook 97 or 98, then look up the Before you can obtain the Outlook-object, you need to add another header-file called OutlookInterface.h in which you put the following: #ifndef _OUTLOOKINTERFACE_H #define _OUTLOOKINTERFACE_H #if defined(WIN32) && !defined(MAC) #ifndef __IOutlookExtCallback_FWD_DEFINED__ #define __IOutlookExtCallback_FWD_DEFINED__ typedef interface IOutlookExtCallback IOutlookExtCallback; #endif /* __IOutlookExtCallback_FWD_DEFINED__ */ // Outlook defines this interface as an alternate to IExchExtCallback. #ifndef __IOutlookExtCallback_INTERFACE_DEFINED__ #define __IOutlookExtCallback_INTERFACE_DEFINED__ EXTERN_C const IID IID_IOutlookExtCallback; interface DECLSPEC_UUID("0006720D-0000-0000-C000-000000000046") IOutlookExtCallback : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE GetObject( /* [out] */ IUnknown __RPC_FAR *__RPC_FAR *ppunk) = 0; virtual HRESULT STDMETHODCALLTYPE GetOfficeCharacter( /* [out] */ void __RPC_FAR *__RPC_FAR *ppmsotfc) = 0; }; DEFINE_GUID(IID_IOutlookExtCallback, 0x0006720d, 0x0000, 0x0000, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46); #endif /* __IOutlookExtCallback_INTERFACE_DEFINED__ */ #endif // defined(WIN32) && !defined(MAC) #endif // _OUTLOOKINTERFACE_H So, to obtain that precious Outlook-object, you would add the following to your class: #include "OutlookInterface.h" class CMyAddin : public IExchExt { public: CMyAddin(); ~CMyAddin(); : : : HRESULT GetOutlookApp(IExchExtCallback *pmecb, Outlook::_ApplicationPtr &rOLAppPtr); Outlook::_ApplicationPtr m_OLAppPtr; }; And implement it as follows: HRESULT CMyAddin::GetOutlookApp(IExchExtCallback *pmecb,
Outlook::_ApplicationPtr &rOLAppPtr)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
try
{
IOutlookExtCallback *pOutlook = NULL;
HRESULT hRes = pmecb->QueryInterface(IID_IOutlookExtCallback,
(void **) &pOutlook);
if (pOutlook)
{
IUnknown *pUnk = NULL;
pOutlook->GetObject(&pUnk);
LPDISPATCH lpMyDispatch;
if (pUnk != NULL)
{
hRes = pUnk->QueryInterface(IID_IDispatch,
(void **) &lpMyDispatch);
pUnk->Release();
}
if (lpMyDispatch)
{
OLECHAR * szApplication = L"Application";
DISPPARAMS dispparamsNoArgs = {NULL, NULL, 0, 0};
DISPID dspid;
VARIANT vtResult;
lpMyDispatch->GetIDsOfNames(IID_NULL, &szApplication, 1,
LOCALE_SYSTEM_DEFAULT, &dspid);
lpMyDispatch->Invoke(dspid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_METHOD,
&dispparamsNoArgs, &vtResult, NULL, NULL);
lpMyDispatch->Release();
rOLAppPtr= vtResult.pdispVal;
return S_OK;
}
}
}
catch(...)
{
}
return S_FALSE;
}
So, go back to your STDMETHODIMP OlemAddin::Install(IExchExtCallback *lpExchangeCallback,
ULONG mcontext,
ULONG ulFlags)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
HRESULT hRet = S_FALSE;
try
{
m_ulContext = mcontext;
GetOutlookApp(lpExchangeCallback, m_OLAppPtr); // <--Add this line here
Ok, so what do we have here? We have an extension, that at 'Install'-time obtains an Outlook-object. Let's see what we can do with it now and how we can actually create a toolbar and add a button to it. First step is to create some sort of container-class for the button(s) we are going to add, this class must be derived from class COutlookButton : public IDispatch { public: COutlookButton(CComPtr <Office::CommandBarControl> pButton); virtual ~COutlookButton(); // IUnknown Implementation virtual HRESULT __stdcall QueryInterface(REFIID riid, void ** ppv); virtual ULONG __stdcall AddRef(void); virtual ULONG __stdcall Release(void); // Methods: // IDispatch Implementation virtual HRESULT __stdcall GetTypeInfoCount(UINT* pctinfo); virtual HRESULT __stdcall GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo); virtual HRESULT __stdcall GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rgDispId); virtual HRESULT __stdcall Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr); BOOL SetupConnection(IDispatch *pDisp); void ShutDown(); DWORD m_dwCookie; CComPtr <Office::CommandBarControl> m_pButton; IConnectionPoint *m_pCP; ULONG m_cRef; }; Now, to save some space in this article, I will not put the source for that class... it's kind of long, so if you have opened up the workspace for this article, then open the file OutlookButton.cpp and I will try explain the logic. When we create an object of this type, we pass it a button as parameter. This button will be instantiated and we will hook up to the In order to keep the code readable, we'll add a function called class CMyAddin : public IExchExt { public: CMyAddin(); ~CMyAddin(); : : : HRESULT InstallInterface(IExchExtCallback *lpExchangeCallback); The function And all we need to do now is to call that function, and we do that from the STDMETHODIMP CMyAddin::Install(IExchExtCallback *lpExchangeCallback,
ULONG mcontext,
ULONG ulFlags)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
:
:
case EECONTEXT_VIEWER:
{
:
InstallInterface(lpExchangeCallback); // <--Add this line here
}
break;
Now compile the whole thing and once again, sort errors (if any) out. So, the only thing you need to do now is to register the DLL and to do this, in the sources for this article, you have a registry-file. You will need to edit that one and change the path so that it points to this DLL. In the previous two parts, we've created a basic addin that added a toolbar, and when you clicked it, displayed a message box. So in this article, we will explore some more things you can do with an addin for Outlook. With this article, I will show you how you can log all incoming mails to a file. Not very impressive but it's only to show you some basic MAPI-operations. If you haven't read the previous articles, then go back to them otherwise you wont understand very much of this. Extending Outlook - Getting called for every mail that comes in...First, let's extend our class to derive from There is one thing that is extremely important and I can't stress enough about, and that is the return value of this function. If you look in MSDN, you can read the following:
Now, read the I think you got the message by now. So let's get going on the code. First, extend your class so that it looks like this (add the lines in bold) class CMyAddin : public IExchExt, IExchExtSessionEvents { public: CMyAddin(); ~CMyAddin(); STDMETHODIMP_(ULONG) AddRef(); STDMETHODIMP_(ULONG) Release(); STDMETHODIMP QueryInterface(REFIID riid, void** ppvObj); STDMETHODIMP Install(IExchExtCallback *lpExchangeCallback, ULONG context, ULONG ulFlags); STDMETHODIMP OnDelivery(IExchExtCallback *lpExchangeCallback); Before we implement the Once again, add the lines in bold STDMETHODIMP CMyAddin::QueryInterface(REFIID riid,
void** ppvObj)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
*ppvObj=NULL;
IUnknown* punk=NULL;
if (riid == IID_IUnknown)
{
punk = (IExchExt*)this;
}
else if (riid == IID_IExchExt)
{
punk = (IExchExt*)this;
}
else if (riid == IID_IExchExtSessionEvents)
{
punk = (IExchExtSessionEvents*)this;
}
else
{
We have specified in the registry that we implement the So, over to the STDMETHODIMP CMyAddin::OnDelivery(IExchExtCallback *lpExchangeCallback)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
try
{
}
catch(...)
{
}
return S_FALSE;
}
Now, what we want to do is to get a pointer to the mail, extract some information and log it to a file. So first we'll ask Outlook for the pointer by calling the method STDMETHODIMP CMyAddin::OnDelivery(IExchExtCallback *lpExchangeCallback)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
try
{
// The first thing we need to do is to obtain a pointer to the message
// that just arrived.
// When we do that, we also obtain a pointer to the message-store (see
// it as a database)
LPMESSAGE lpMessage = NULL;
LPMDB lpMdb = NULL;
if (SUCCEEDED(lpExchangeCallback->GetObject(&lpMdb,
(LPMAPIPROP*)&lpMessage)))
{
The method Before we continue, let's take a look at what a message is and how the data is stored in it. A MAPI-message is nothing like a mime-message. It's a binary blob and not a text-message. The data is stored in something called tags, propertytags. You could compare this to an ini-file where you have keys and values. In this case, the keys are tags. These tags can either be predefined by MAPI, the most common ones like the subject and the body is a predefined tag, but you can also have something called named tags. Named tags are tags that you define in which you can store data that is private. This does not mean that the data is encrypted or hidden, anyone can extract this data. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||