When to release COM objects?

Add-in Express™ Support Service
That's what is more important than anything else

When to release COM objects?
 
David Liebeherr


Guest


Hi,

I have a couple of questions about releasing COM objects in a Outlook Addin.

1. Do I need to relase COM objects returned by a Interop Method even when not using the returned object?
When I do something like this:

var application = (Application)ADXAddinModule.CurrentInstance.HostApplication;
var mailItem = (MailItem) application.CreateItem(OlItemType.olMailItem);
mailItem.Attachments.Add("Path...");

Attachments.Add() will return an instance of the Attachment class, which is a COM object.
Do I have to put the returned Attachment instance into a variable and also release this instance via Marshal.ReleaseCOMObject()?
In other words: When a method is called that returns a COM object do I need to take care of realsing this object even if I do not do anything with the returned object (not even putting it in a variable)?

2. How do I know which object is a COM object?
When working with the Outlook/Word Object Model you have to call a lot of Methods and Properties.
How do I know if a Object returned by a method or property of an Object Model class is a COM object?

Assume the following example:

Application application = (Application)ADXAddinModule.CurrentInstance.HostApplication;
Inspector inspector = application.ActiveInspector();
Document document = inspector.WordEditor as Document;
Windows windows = document.Windows;
Window window = windows[1];
Microsoft.Office.Interop.Word.Selection selection = window.Selection;
selection.InsertAfter("Text");

In this example I have 6 objects that I got from the Object Model.
How do I know which of them are COM objects (that I need to take care of releasing)?
For example: Is selection a COM object?

Or can I assume that each and every Object returned by a method/property from a Object Model class will be a COM object (apart from things like string, int, etc. of course)?

3. When relasing a "parent" COM object do I need to relase the "child" COM objects also?
Assume the following code:

Application application = (Application)ADXAddinModule.CurrentInstance.HostApplication;
NameSpace session = application.Session;
Accounts accounts = session.Accounts;
foreach (var account in accounts)
{                
}

In this example when I relase the accounts object, do I also have to relase all Account instances I have itereated over in the foreach loop?
Or will relasing the Accounts object also relase the Account objects automatically?

Thank you very much!

Sincerely yours,
David Liebeherr
Posted 24 May, 2013 13:01:07 Top
nwein




Posts: 577
Joined: 2011-03-28
Hey David, I can answer 2 of your questions:
2. How do I know which object is a COM object?

You can use
Marshal.IsComObject(myObject)

3. When relasing a "parent" COM object do I need to relase the "child" COM objects also?

Yes.
Moreover, using foreach with COM objects is not recommended. Use for loop instead.
I can see from your code that you're not using 2 dots which is a good thing (i.e. 2 dots for COM objects is bad).

Hope that helps.
I'm sure Andrei et al will reply to you tomorrow as well
Posted 24 May, 2013 13:27:54 Top
David Liebeherr


Guest


Hi nwei,

Thank you very much for your reply.

You can use
Marshal.IsComObject(myObject)


Thank you for the tip.
I was hoping there exist a general rule like "If you get a object from an interop method/property it will always be an COM object if it is not a type from the .NET Base Class Library (like System.String))".

I can see from your code that you're not using 2 dots which is a good thing (i.e. 2 dots for COM objects is bad).


Ah, you mean I am not using a method chaining (e.g. application.Session.Accounts).
First I needed to think about it for a bit to know what you meant by 2 dots :-)
Yeah method chaining can realy cause some issues, because many people are not aware that all objects retrieved in the chain must also be released.

Method chaining can also be quite tricky when dealing with value types (structs), because each time you retrieve an instance of a value type you get a copy of that instance not a reference.

I have often seen code like this:

var person = this.GetPerson();
var address = person.Address; //Address is a struct, not a class
address.Street = "New Address";


In this example you will not change the Stree property of the Address instance that the Person object is holding.
What you actually do is:
1. Get a copy of the Address instance.
2. Change the Street property on THE COPY.

So the Street property of the Address instance held by the Person instance will not be changed.
I have seen many people beeing fooled by this (I was fooled more than once when I was starting with C# :-)).

This is also the reason why the C# compiler won't let you do something like this:

person.Address.Street = "New Street";

The compiler in this case will generate a compiler error "Cannot modify the return value of 'Person.Address' because it is not a variable".
Which is not a realy good error message for people which are new to C# and are not aware of the differences between reference types and value types.

However what you would need to do in this Situation is:

var person = this.GetPerson();
var address = person.Address;
address.Street = "New Street";
person.Address = address;


*Sorry, I know for people who know the characteristics of value types this is a boring text, but I thought it might be good for people not knowing it reading this.

When dealing with COM objects I am using this code style:

public MailItem GetSelectedMail()
{
    Application application = null;
    Explorer activeExplorer = null;
    Selection selection = null;

    try
    {
        application = ADXAddinModule.CurrentInstance.HostApplication as Application;

        if (application != null)
        {
            activeExplorer = application.ActiveExplorer();

            if (activeExplorer != null)
            {
                selection = activeExplorer.Selection;
                if (selection.Count > 0 && selection.Class == OlObjectClass.olMail)
                {
                    return selection[1] as MailItem;
                }
            }

            return null;
        }
    }
    finally
    {
        if (selection != null)
            Marshal.ReleaseComObject(selection);

        if (activeExplorer != null)
            Marshal.ReleaseComObject(activeExplorer);

        if (application != null)
            Marshal.ReleaseComObject(application);
    }
}
Posted 24 May, 2013 14:29:59 Top
Nicholas Hebb


Guest


application = ADXAddinModule.CurrentInstance.HostApplication as Application;


You should be able to access the application via this code instead:
AddinModule.CurrentInstance.OutlookApp


Note: The code sample on pages 28-29 of the Developer Guide (pdf) does not show them releasing the application object. I assume that Add-in Express releases it when the add-in is shut down.

In the first code block above, you have
mailItem.Attachments.Add("Path...");
. This is one of those tricky cases where you have implicitly created an Attachments object. You should break that out into a separate variable that can be released.

Also, it's a personal preference, but I avoid using var to declare COM objects. It makes it harder to see which COM objects have been created and need to be released when I visually scan my code.
Posted 25 May, 2013 00:05:43 Top
Andrei Smolin


Add-in Express team


Posts: 18817
Joined: 2006-05-11
Thanks to all of you!

Hello David,

#1 - Yes. In whatever way you create a COM object, failing to release the COM object means it will be released by .NET Framework in an unpredictable moment. This may create very hard-to-debug problems.

mailItem.Attachments.Add("Path..."); this creates two COM objects. One of them represents a collection of attrachments of the given mailitem. The second COM object represents the attachment just added to the collection.

You also need to release the just added member if you call Actions.Add(), AddressEntries.Add(), Categories.Add(), Items.Add(), Folders.Add(), Recipients.Add(), UserProprties.Add() etc. Also, pay attention to releasing return values of these methods: Copy(), Move(), CopyTo(), MoveTo(), Forward(), Relpy(), ReplyAll(), ForwardAsVcal(), Respond() etc.

#2 - Marshal.IsComObject(...). Actually, you are right: if you open the VBA object broswer then every property (method) returning an object returns a COM object. Properties/methods returning base types and enums do not create COM objects. There are exclusions to the rule see e.g. the OutlookBarShortcut.Target property (http://msdn.microsoft.com/en-us/library/office/ff868031.aspx).

Getting Help on COM Objects, Properties and Methods
To get assistance with host applications?Â?Ð?é objects, their properties, and methods as well as help info, use the Object Browser. Go to the VBA environment (in the host application, choose menu Tools | Macro | Visual Basic Editor or just press {Alt+F11}), press {F2}, select the host application in the topmost combo and/or specify a search string in the search combo. Select a class /property /method and press {F1} to get the help topic that relates to the object.


#3 - Yes. As Nir pointed out, using foreach is not recommended. Please check this blog: http://blogs.msdn.com/b/mstehle/archive/2007/12/07/oom-net-part-2-outlook-item-leaks.aspx.

On your code.

1. selection = activeExplorer.Selection; - note that getting selection produces an exception if the current folder is a special folder such as RSS Feeds; in earlier Outlook versions this also breaks when in a top-level folder such as Presonal Folders.

2. if (selection.Count > 0 && selection.Class == OlObjectClass.olMail) - I believe selection.Class returns OlObjectClass.olSelection; OlObjectClass.olMail should be returned by an item in the selection.


Andrei Smolin
Add-in Express Team Leader
Posted 27 May, 2013 02:17:30 Top
Nicholas Hebb


Guest


Hi Andrei,

The main reason I was interested in this thread is my comment here:

Nicholas Hebb writes:
Note: The code sample on pages 28-29 of the Developer Guide (pdf) does not show them releasing the application object. I assume that Add-in Express releases it when the add-in is shut down.


In my case I'm using the following class as a shortcut to the Excel application object, but I never release it. I just wanted to confirm that when we use the AddinModule.CurrentInstance.OutlookApp (or ExcelApp, WordApp, etc), that we do not need to call Marshal.ReleaseCOMObject on these objects. Is that correct?


public static class XL
{
    public static Excel._Application App
    {
        get { return AddinModule.CurrentInstance.ExcelApp; }
    }

    public static int Version
    {
        get { return AddinModule.CurrentInstance.HostMajorVersion; }
    }
}
Posted 27 May, 2013 02:59:30 Top
Andrei Smolin


Add-in Express team


Posts: 18817
Joined: 2006-05-11
Hi Nicholas,

I'm sorry for missing this part. Yes, of course Add-in Express releases the Application object at add-in shutdown; this is the last operation before the add-in gets unloaded.


Andrei Smolin
Add-in Express Team Leader
Posted 27 May, 2013 03:06:36 Top
Nicholas Hebb


Guest


Hi Andrei,

Thanks for confirming that.
Posted 27 May, 2013 07:05:34 Top
David Liebeherr


Guest


Hi,

thank you Andrei, Nicholas and nwein for all the usefull tips!

I have just created a IDisposable Wrapper for COM Objects:

public class ComReference<T> : IDisposable
{
    private static readonly Logger logger;

    static ComReference()
    {
        logger = LogManager.GetLogger("ComReference");
    }

    private readonly T reference;

    private Boolean isDisposed;

    public T Reference
    {
        get { return this.reference; }
    }

    public ComReference(T reference)
    {
        if (logger.IsEnabled(LogLevel.Trace))
            logger.Log(LogLevel.Trace, "Creating");

        this.reference = reference;
    }

    ~ComReference()
    {
        if (!this.isDisposed)
        {
            if (logger.IsEnabled(LogLevel.Warn))
                logger.Log(LogLevel.Warn, "This instance of ComReference<{0}> is finialized before Dispose() was called.", typeof(T).FullName);
        }
    }

    public void Dispose()
    {
        if (this.isDisposed)
        {
            throw new InvalidOperationException(Stri ng.Format("This instance of ComReference<{0}> is already disposed.", typeof(T).FullName));
        }

        if (this.reference != null && Marshal.IsComObject(this.reference))
        {
            Marshal.ReleaseComObject(this.reference);
        }

        this.isDisposed = true;
    }
}


It can be used like this:

using (var mailItemReference = new ComReference<MailItem>(mailItem))
{
    mailItemReference.Reference.Subject = "Test";
}


What you you think of that?
Are there any flaws in doing so or have i missed anything?

Thank you.

Sincereley yours,
David Liebeherr
Posted 27 May, 2013 08:31:16 Top
Andrei Smolin


Add-in Express team


Posts: 18817
Joined: 2006-05-11
Hello David,

If this class is convenient for you, then it is okay. I haven't check the code; I assume it's correct.

I tried to create such a class myself. I watched several attempts to create a similar class. Such classes tend to introduce extra rules to support different scenarios; the rules are not obvious (usually).

To explain things simply, we use ReleaseComObject() in the manual and in the code of Add-in Express. Although a bore sometimes, this is an explicit way of doing things.


Andrei Smolin
Add-in Express Team Leader
Posted 27 May, 2013 10:55:40 Top