I remember working in Richmond, VA for years thinking how awesome it would be to work at Microsoft. It always seemed so unrealistic - even if I got past the interview process, I'd still have to move across the country. Not only would it be a hard opportunity to get but it would be a big personal sacrifice to move. Well all that changed when I heard about a job in Charlotte, NC with Microsoft. Microsoft was in Charlotte all this time?! I had no idea. It changed everything for me. I learned about a whole new world of Microsoft that wasn't product development but product support. Helping people find the right menu item so that they can correctly format text or send an email in Outlook didn't sound too exciting to me but helping some of Microsoft's biggest ISV and business customer's make design decisions and fix their Exchange and Outlook applications was worth investigating. I found that Developer Support, for me, was a great blend of what I like to do with technology: be creative, learn new things, and communicate with people. I could do all this without the pressure of unrealistic deadlines laid on me by a bad project specifications/contracts, without travelling to a new city for each project, and with the comfort of the best benefits in the business!
We just hired Rick and we are looking for more good folks who want to work in either Charlotte, Las Colinas, or Fargo on our team. We are looking for .NET and/or C++ developers with good communication skills, a thirst for knowledge, and the ability to have effective technical discussions with peers and customers. We don't expect candidates to have Exchange or Outlook development experience necessarily - we can teach you that, you just have to be ready to learn! Feel free to email or IM me if you think you might be interested in a job in Developer Support for Office or Exchange and want to know more...
Exchange 2007 only adds 64-bit components in a 64-bit install, when you install Exchange it will register CDOEX.DLL which will replace CDOSYS.DLL and be used when you instantiate CDO.Message. So you will see CDO.Message pointing to "C:\Program Files\Common Files\Microsoft Shared\CDO\CDOEX.DLL" in the 64-bit registry and you will see CDO.Message pointing to "C:\WINNT\SysWow64\cdosys.dll" in the 32-bit registry. If your application is a 64-bit process it will load up CDOEX.DLL when you instantiate CDO.Message. If your application is a 32-bit process it will load up CDOSYS.DLL when you instantiate CDO.Message.
After installing 64-bit Exchange 2007 you may notice that 32-bit scripts and applications get a "Library not found" error when using CDOSYS. This appears to be a side effect of registering the 64-bit CDOEX.dll. To resolve the issue simply open a 32-bit instance of cmd.exe from the SysWow64 directory and run "regsvr32 cdosys.dll" to re-register the 32-bit CDOSYS.
Outlook item leaks are the most common OOM with .NET issues that we see and I’ve debugged enough of them to compile this list of the four basic mistakes that contribute to item leaks. An “item leak” is most commonly seen as an item that won’t refresh or can’t be saved in the Outlook UI. A common example of this problem is that a user will complain that they receive a dialog stating that they can’t save or update an appointment in Outlook when a particular AddIn is loaded. This is because the AddIn has not released the item reference and Outlook is reusing it. In order to fix the problem properly you need to look at your code for a few of the common mistakes listed below…
Outlook Processes Reuse Cached Item References
If an item is in memory in Outlook.EXE then Outlook will reuse it when another action requests it. For example, if a COM AddIn leaks an item reference and a user is interacting with that item through the UI they might not be able to save their changes all of a sudden. This is especially true if the item were to be modified in the MAPI store after it has been leaked. An easy test would be to modify the suspected leaked item using MFCMAPI while Outlook is running then open the item in the Outlook UI and try to modify it. You will get a dialog saying that the item cannot be saved if you have leaked the item.
Do Not Chain Child Object References
One of the advantages of pure .NET programming is that you don't have to worry about memory management because the garbage collector and the dispose pattern handle all of that. What we have to remember that OOM programming in .NET is not pure .NET programming ***it is COM programming in .NET*** because of that the rules of COM apply here. Every object referenced must be released. When you chain objects you lose track of a reference that you need to release.
See the example below...
// This leaks an Attachments reference
ProcessAttachments(email.Attachments);
// This doesn’t leak
Outlook.Attachments oAttachs = email.Attachments;
ProcessAttachments(oAttachs);
Marshal.ReleaseCOMObject(oAttachs);
oAttachs = null;
Since the reference to the Attachments object is maintained it can be released properly.
Leaking a Child Item Leaks the Parent
If a child item that holds an internal reference to its parent – such as attachments or recipients - is leaked then the parent is in turn leaked. In the example above, chaining and leaking the child Attachments collection of the email will cause the email itself to leak. The parent object will never get cleaned up regardless of how many times GC.Collect or ReleaseCOMObject are called.
Be Careful with foreach Loops
The basic use of a foreach loop to iterate through items in a collection that have an internal reference to their parent – see above - will leak items. Either avoid foreach loops altogether by using for loops...
// This leaks, GC.Collect will not help
foreach (Outlook.Attachment oAttach in oAttachs)
{
Marshal.ReleaseCOMObject(oAttach);
oAttach = null;
}
for (int i = 1; i < oAttachs.Count; i++)
for (int i = 1; i =< oAttachs.Count; i++)
Outlook.Attachment oAttach = oAttachs[i];
// Do nothing with attachment
…or follow the notes in this discussion to articulate the handling and cleanup of the underlying objects in the enumerator created by the foreach loop to ensure that things get cleaned up properly…
IEnumerator attachsEnum = oAttaches.GetEnumerator();
while (attachsEnum.MoveNext())
Attachment at = (Attachment)attachsEnum.Current;
Marshal.ReleaseCOMObject(at);
ICustomAdapter adapter = (ICustomAdapter)attachsEnum;
Marshal.ReleaseComObject(adapter.GetUnderlyingObject());
Marshal.ReleaseComObject(oAttaches);
*** Edited on 7/31/2009 to fix attachment loop, thanks Tobias!
OOM.NET is not a special API set that was created in managed code. It is the name I've given to a series of posts I'll do about the “gotchas” of Outlook Object Model development in .NET. I've compiled some notes over time of the most common issues, how to resolve them, and why they exist. I'd love to hear back from you on other issues you face with OOM programming in .NET as well so that I can add them to this series...
With the introduction of Visual Studio Tools for Office and the maturity of .NET languages in general more and more Outlook developers are using .NET languages like C# and VB.NET. The nice thing about .NET is that it encapsulates a lot of common functionality into a framework, manages memory, provides a framework for inter-operating with COM, and allows you to quickly write applications with very little concern for how all this happens.
The bad thing about .NET is that it allows you to quickly write applications with very little concern for how all that happens...
Developers can get caught up in the ease and speed of development in .NET. They can suffer from an "It Just Works Addiction" - meaning in the bliss of swiftly writing code that just happens to compile and run it is possible to lose sight of what is making everything work. This mentality simply will not work with Outlook development; especially in .NET because there is just too much that can happen to adversely affect your application. So on with the show...
Why Events Stop Firing
Typically, the events stop firing problems come from adding an event handler from a locally scoped object or calling ReleaseCOMObject on an object which you are listening to events on. The code below will eventually stop handling the NewInspector event because inspectors is locally scoped here and when GC runs it will get cleaned up thus stopping the event to fire…
private void ThisAddIn_Startup(object sender, EventArgs e)
Outlook.Inspectors inspectors;
inspectors = Application.Inspectors;
inspectors.NewInspector += Inspectors_NewInspector;
To fix it you would need to make inspectors a module level variable and call ReleaseCOMObject within a dispose method or in this case inside ThisAddIn_Shutdown. You wouldn’t want to call ReleaseCOMObject on inspectors within ThisAddIn_Startup because it would free the underlying COM object and also cause your event to stop firing.
private Outlook.Inspectors inspectors;
However, in Inspectors_NewInspector you *would* want to call ReleaseCOMObject on inspector once you were done with it. For example…
private void Inspectors_NewInspector(Outlook.Inspector inspector)
try
MessageBox.Show(inspector.Caption);
finally
Marshal.ReleaseComObject(inspector);
You wouldn’t want to call ReleaseCOMObject until you were done with that particular reference though. In this case all the work is done within the try/finally block so we can safely release when we are done. Also, it is highly recommended that you use a try/finally block to release objects in this scenario because it ensure the reference will get released even if there is an exception.