Tuesday, April 7, 2015

Word/Excel Properties returning NULL/Nothing, Inside Event Sinks or COM Callbacks

While working with a VSTO addin (Excel/Word Automation with .NET), I’ve faced a strange issue. The context is described here. We had a VSTO Word Ribbon AddIn, for having a word application level customization. Within the AddIn we also consume another COM Object’s Event Sources (COM event interfaces) through Event Sinks (COM Callbacks).

Within the callback function, we are trying to access word objects properties like Active Document, Document Properties, Custom document properties e.t.c. But all properties are returning NULL values for our surprise. There is no clue, regarding why its not able to access the properties and its even not throwing any error as well.

After much troubleshooting its found that the issue lies with, how .NET manages COM callbacks. By default .NET uses the built in ‘Free Threaded Marshaller’ (MTA – Multi Threaded Apartments) to handle COM Events to invoke the sinks. It will pick up a random RPC thread to handle the incoming COM event, and the Callback will be executed on this random RPC thread. COM objects in MTA are not thread safe by default. We should use our own synchronization mechanism to synchronize the access.

On the other hand, most of the COM components, that having GUI elements (like Office Word/Excel) lives in STA (Single Threaded Apartment) only, which is by default thread safe. This thread safety will be managed by the COM runtime by itself. It uses and hidden window and associated window messages to synchronize the access.

Now in our context, we’ve two apartments. One is the Office system’s STA and the .NET runtime’s MTA. The callback which is being executed in MTA needs to access the office properties, which lives in STA. COM interface access across apartments, is not directly possible. The needed interface (here Office propertie) should be marshalled to the target apartment. By default this is not available with Office objects. i.e Marshaling Office COM objects from their original STA apartment to the MTA apartment, where the callback want to access the office properties.

Since this marshalling fails, the properties returning as NULLs.

So what could be the solution? The answer is to tell the .NET runtime to use the same office STA apartment for the COM Event handling, so that both Office objects and callbacks will live in the same apartment and no marshalling needed to access the office objects.

To make the ‘Callback object’ to join the STA apartment, we need to inherit our ‘Callback object class’ from ‘StandardOleMarshalObject’ instead of the default ‘Free Threaded Marshaller’. A sample given below.

[ComVisible(true)]

[ClassInterface(ClassInterfaceType.None)]

internal class YourComCallBackClass : StandardOleMarshalObject, AComEventSinkInterface

{

       public short AComEventSinkInterface.EventSinkOrCallbackMethod()

       {

             //Access your office properties here.

             Office.DocumentProperties properties;

             properties =

             Application.ActiveDocument.CustomDocumentProperties as Office.DocumentProperties;

       }

}

ClassInterfaceType.None’ attribute is equally needed to make this work.

You can read more on this in the below links.

StackOverflow

MSDN Blogs

MSDN

No comments:

Post a Comment