RCW Reference counting of Outlook Object

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

RCW Reference counting of Outlook Object
When I release the RCW I get what I think is an incorrect reference count. 
German Hayles




Posts: 41
Joined: 2015-04-27
In the code snippet below i retrieve an object from the Selection collection. I do this is in the "ExplorerSelectionChange" event handler.

I then call ConnectToSelectedItem() using the 'explorer' parameter coming ExplorerSelectionChange.

In ConnectToSelectedItem() I AGAIN retrieve an item from the Selection collection. (Note: each time it is the Outlook._AppointmentItem object).

I would assume that the RCW reference count would be 2 at this point.

When we return to ExplorerSelectionChange and I call ReleaseCOMObject on the Outlook._AppointmentItem object being held in "item" the reference count returned is 0!!

What did I miss? I thought it would be 1.


private void adxOutlookAppEvents1_ExplorerSelectionChange(object sender, object explorer)
{
    Outlook.Selection selection = null;


    try
    {
        selection = ((Outlook._Explorer)explorer).Selection;
        object item = selection[1]; // RCW referece count for Outlook.AppointmentItem is incremented

	if (item is Outlook._AppointmentItem)
	{
	    ConnectToSelectedItem(explorer);
	    EnableFeature( item); //In-House function. Enables/Disables controls based on the properties of the item
	}
       
       int i = Marshal.ReleaseComObject(item);  //<<<<<< I was surprised to see this return 0!!!  
                        
                
    }
    catch (Exception ex)
    {
        System.Diagnostics.Debug.WriteLine(ex.StackTrace);

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

private void ConnectToSelectedItem(object explorer)
{

           
    Outlook.Selection sel = null;
    Outlook._Explorer exp = null;
    Outlook.Folder folder = null;

    if (GetExplorerCount() <= 0)
    {
        return;
    }


    try
    {
        exp = explorer as Outlook._Explorer;
        sel = exp.Selection;

        if (sel.Count == 1)
        {

            
            object item = sel[1]; // RCW referece count for Outlook.AppointmentItem is incremented again?
            

            if (item is Outlook._AppointmentItem)
            {
                if (_ItemEvents.IsConnected) // IGNORE THIS.  JUST ASSUME IT RETURNS FALSE 
                {
   	            // THIS SECTION OF CODE CAN BE IGNORED SINCE THIS IS FIRST TIME GOING
	            // THROUGH HERE AND _ItemEvents.IsConnected IS FALSE
							 //
                    if (IsSameAppointment(_ItemEvents.ItemObj, item) == false)
                    {
                        _ItemEvents.RemoveConnection();
                        _ItemEvents.ConnectTo(item, true);
                    }
                }
                else
                {
                    _ItemEvents.ConnectTo(item, true);
                }
            }
            else
            {
		// IN THIS EXAMPLE WE DON'T REACH THIS SECTION OF CODE
		// SINCE THIS IS AN Outlook._AppointmentItem object
		//
                Marshal.ReleaseComObject(item); 
            }

        }
    }
    catch (Exception ex)
    {
        System.Diagnostics.Debug.WriteLine(ex.StackTrace);
    }
    finally
    {
        if (sel != null) { Marshal.ReleaseComObject(sel); sel = null; }
    }
}
Posted 12 Jan, 2017 12:03:57 Top
Andrei Smolin


Add-in Express team


Posts: 18823
Joined: 2006-05-11
Hello German,

object item = sel[1];


This line creates a COM object and sets it to the item variable. As part of the creation process Incrementing the reference counter for this COM object sets the reference counter to 1. Calling ReleaseComObject on this variable decrements the reference counter; in other words, ReleaseComObject returns zero for *this* variable.

Getting the *same* item one more time creates *another* COM object having a *different* reference counter. Once created, that COM object also has its reference counter set to 1 and releasing *this* COM object zeroes the reference counter.


Andrei Smolin
Add-in Express Team Leader
Posted 13 Jan, 2017 10:35:37 Top
German Hayles




Posts: 41
Joined: 2015-04-27
Very interesting.

Would it be correct to assume that had the "explorer" object been passed as a reference to "ConnectToSelectedItem" that a separate COM object would NOT have been created and the SAME RCW that was maintaining the reference count in "SelectionChange" would be maintaining the reference count in "ConnectToSelectedItem"?


THANK YOU for your attention to these irritating details. It's greatly appreciated.
Posted 13 Jan, 2017 11:53:37 Top
Andrei Smolin


Add-in Express team


Posts: 18823
Joined: 2006-05-11
Hello German,

Correct.

Here is a sketch highlighting the use of the explorer variable in your code:

private void adxOutlookAppEvents1_ExplorerSelectionChange(object sender, object explorer) 
{ 
...
        ConnectToSelectedItem(explorer); 
...         
} 
 
private void ConnectToSelectedItem(object explorer) 
{ 
    Outlook._Explorer exp = null; 
...
        exp = explorer as Outlook._Explorer; 
        // use exp
...
} 


First off, a .NET variable referencing a COM object internally references an RCW object (this is a .NET object as well), which, in its turn, stores a reference to the COM object and manages an internal reference counter. Releasing the .NET variable via Marshal.ReleaseComObject() makes the RCW decrement the reference counter; if the counter becomes zero, the RCW releases the COM object. If you use the .NET variable which is already released, you get this exception "COM object that has been separated from its underlying RCW cannot be used".

The explorer variable is passed to the adxOutlookAppEvents1_ExplorerSelectionChange method; further on I assume it is passed by the ADXOutlookAppEvents component. Then explorer is passed to another method, ConnectToSelectedItem. Since you pass a reference to the object, there's no new RCW created. Similarly, there's no new RCW when you cast the .NET variable in the code of ConnectToSelectedItem.

In other words, the exp variable refers to the same RCW as the explorer variable! This is essential - releasing exp in the code of ConnectToSelectedItem would raise an exception if adxOutlookAppEvents1_ExplorerSelectionChange would try to use the explorer variable after the call. Similarly, using an internal variable referencing the same RCW would raise an exception in the code of the ADXOutlookAppEvents component!

This is why you must follow this rule when using Add-in Express: Never release COM objects obtained through the parameters of events provided by Add-in Express.


Andrei Smolin
Add-in Express Team Leader
Posted 16 Jan, 2017 07:14:39 Top
German Hayles




Posts: 41
Joined: 2015-04-27
I tried an experiment. I passed explorer by reference, and then by value. From this example it seems like it doesn't make a difference whether or not I pass explorer by value or by reference. Each time a separate RCW with it's own ref count is created.

Are there unwarranted assumptions I'm making?




        private void adxOutlookAppEvents1_ExplorerSelectionChange(object sender, object explorer)
        {
           
            Outlook.Selection selection = null;
            object item = null;
            try
            {
                selection = ((Outlook._Explorer)explorer).Selection;

                if ( selection.Count == 1)
                {
                    item = selection[1];

                    if (item is Outlook._AppointmentItem)
                    {
                        ByReference( ref explorer);
                        int itemRefCount = Marshal.ReleaseComObject(item);  //REF COUNT is 0 after this call.  I was expecting it to be 2.


                        ByValue(explorer);
                        itemRefCount = Marshal.ReleaseComObject(item); //REF COUNT IS -1 after this call.
                      
                    }

                    else
                    {
                        int refCount =Marshal.ReleaseComObject(item);
                    }
                    

                   
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.StackTrace);

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

        private void ByReference( ref object explorer)
        {
            Outlook.Selection selection = null;
            object refItem1 = null;
            object refItem2 = null;
            object refItem3 = null;


            selection = ((Outlook._Explorer)explorer).Selection;
            object refItem = selection[1]; //REF COUNT = 1

            if (refItem is Outlook._AppointmentItem)
            {
                refItem1 = selection[1]; //REF COUNT = 2
                refItem2 = selection[1]; //REF COUNT = 3
                refItem3 = selection[1]; //REF COUNT = 4
            }

            int refRefCount = Marshal.ReleaseComObject(refItem);  //REF COUNT = 3
        }

        private void ByValue(  object explorer )
        {
            Outlook.Selection selection = null;
            object valItem1 = null ;
            object valItem2 = null;
            object valItem3 = null;

            selection = ((Outlook._Explorer)explorer).Selection;
            object valItem = selection[1]; // REF COUNT = 1

            if (valItem is Outlook._AppointmentItem)
            {
                valItem1 = selection[1]; // REF COUNT = 2
                valItem2 = selection[1]; // REF COUNT = 3
                valItem3 = selection[1]; // REF COUNT = 4
            }

            int valRefCount = Marshal.ReleaseComObject(valItem);  //REF COUNT = 3

        }
Posted 17 Jan, 2017 16:08:33 Top
Andrei Smolin


Add-in Express team


Posts: 18823
Joined: 2006-05-11
Hello German,

German Hayles writes:
From this example it seems like it doesn't make a difference whether or not I pass explorer by value or by reference.


That's correct. In my previous post, mentioning passing by reference was a mistake. I'm sorry for this.

As to your results, whenever you call selection[1], this creates a new COM object having its reference counter set to 1. That is, the ByReference and ByValue methods create and leave unreleased three COM objects.

In this respect, the code of the adxOutlookAppEvents1_ExplorerSelectionChange method can be replaced with the code below. What surprises me is ReleaseComObject decrementing the reference counter even if it is equal to zero.

private void adxOutlookAppEvents1_ExplorerSelectionChange(object sender, object explorer) {

    Outlook.Selection selection = null;
    object item = null;
    try {
        selection = ((Outlook._Explorer)explorer).Selection;

        if (selection.Count == 1) {
            item = selection[1];

            if (item is Outlook.MailItem) {
                int itemRefCount = Marshal.ReleaseComObject(item);  //REF COUNT is 0 after this call.  
                itemRefCount = Marshal.ReleaseComObject(item); //REF COUNT IS -1 after this call. 
            } else {
                int refCount = Marshal.ReleaseComObject(item); // //REF COUNT is 0 after this call.
            }
        }
    } catch (Exception ex) {
        System.Diagnostics.Debug.WriteLine(ex.StackTrace);

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



Andrei Smolin
Add-in Express Team Leader
Posted 18 Jan, 2017 08:04:35 Top
German Hayles




Posts: 41
Joined: 2015-04-27
Hmm. This has raised a few questions for me. I'm going to fire up my debugger and get back to this in a couple of days.

We have a few projects that are resource intensive and it's imperative that I get this right.
Posted 20 Jan, 2017 10:36:24 Top
Andrei Smolin


Add-in Express team


Posts: 18823
Joined: 2006-05-11
Hello German,

Feel free to ask me when you need to.

Have a nice weekend.


Andrei Smolin
Add-in Express Team Leader
Posted 20 Jan, 2017 11:00:52 Top