Common Pitfalls of COM Interoperability in .Net







Introduction


Introduction

 

COM Interoperability
is an important feature of .Net; this allows us to use COM objects in .Net and
vice versa. This feature helps us to develop new applications still making use
of old components that were written as COM. What more even to interact with MS
Office applications (ex – Office 2003) we need to use interoperability.

 

Interoperability
is a powerful feature, with power comes responsibility. Code written in .NET
runs under managed environment, where as COM objects run as unmanaged components.
There are several common pitfalls that the .Net developers using
interoperability have to be aware of. This would save the valuable development
time that would otherwise be wasted to debug the weird issues encountered using
interoperability.


let us look at the common pitfalls of using COM interoperability. This discussion focuses mainly on treatment of COM objects in our code.

 

Scenario
1: Passing COM objects between functions

 

It is not
recommended to pass the COM objects between functions. It would be always
better if the COM objects are created in local scope and release immediately.

However
if there is requirement to pass the COM object between functions, one must be
aware of the following scenario.

 

If COM
objects are passed

a) The received objects are not released.

b) Creator always releases the objects.

 

Look at
the following code snippet

 


Wrong
Usage:


 

public partial
class SalesDemo
: Form


{

   public
SalesDemo()

   {

 

     
InitializeComponent();

 


      Invoice l_oInvoice =
new Sales.InvoiceClass();

 


     
WrongParameterHandeling(l_oInvoice);

     
l_oInvoice.ProductObject.GetProductCode();

   }

 

   public void WrongParameterHandeling(Invoice
p_oInvoice)

   {

      try

      {

         //Use
ComObject

 

      }

      finally

      {

  System.Runtime.InteropServices.Marshal.ReleaseComObject(p_oInvoice);

      }

   }

}

 

The code
when executed throws up an exception

 

System.Runtime.InteropServices.InvalidComObjectException
was unhandled

Message=”COM
object that has been separated from its underlying RCW cannot be

used.”

 

Text Box: Reason

 

 

This is
one of the most common exceptions with COM interoperability; this occurs when
we release the COM objects and try to make a call to the COM object through the
wrapper class.

 

In the
above shown snippet the function
WrongParameterHandeling has released the COM
object, hence there after references of the Wrapper classes will no longer hold
the reference to the COM object and the statement

 

l_oInvoice.ProductObject.GetProductCode();

 


throws the Invalid COM object exception.


Correct
Usage:

 

namespace COMInteropDemo

{


   public partial
class SalesDemo
: Form


   {

      public
SalesDemo()

      {

        
InitializeComponent();

 

        
Invoice

l_oInvoice = new Sales.InvoiceClass();

        
CorrectParmHandeling(l_oInvoice);

Text Box: COM Object released by creator
        
l_oInvoice.ProductObject.GetProductCode();

 



System.Runtime.InteropServices.Marshal.ReleaseComObject(l_oInvoice);

      }

 

      public void CorrectParmHandeling(Invoice
p_oInvoice)

      {

        try

        {

          
//Use
ComObject

        }

       
catch
(Exception e)

        {

 

        }

 

      }

  }

}

 

Scenario
2: Events from COM objects

 

If events
are subscribed from COM objects make sure they are unsubscribed in relevant
functions.

 

public partial
class SalesDemo
: Form


{

 

   public
SalesDemo()

   {

     
InitializeComponent();

 

      Invoice l_oInvoice =
new Sales.InvoiceClass();

     
CorrectParmHandeling(l_oInvoice);

Text Box:  COM Object Created      l_oInvoice.ProductObject.GetProductCode();

 

Text Box: Even subscribed, G_oProduct is a Global object and has life even after the Form is out of scopeG_oProdcut.PriceUpdated += new
__Product_PriceUpdatedEventHandler(G_oProdcut_PriceUpdated);

 


System.Runtime.InteropServices.Marshal.ReleaseComObject(l_oInvoice);

   }

 


Text Box:  Subscribe the COM event   void
l_oProdcut_PriceUpdated(ref short NewPrice)

   {

      //Implement
logic

   }

 

When we run the above code the works as expected, but
once we move out of the form and form goes out of scope, un expected results
are got next time the
PriceUpdated event gets fired.

 

 

 


Even listeners remain in memory even after they are
out of scope (but not claimed by GC) and react to the events when they occur
leading to unexpected results.

 

 

Unsubscribe all the com events in relevant
functions. In case of a form same can be done in Dispose method.

 

Scenario 3: intermediate COM objects in the code

It is not a good practice to use intermediate COM
objects in code, one can generally pull it off in standalone applications but
it the same is a service, and this could have -ve impact.

 

 


Wrong
Usage:

 

l_oInvoice.ProductObject.GetProductCode();

 

 

 

 


A reference to the intermediate object is created
in .Net. This object cannot be released
as no reference is available. Releasing the main object does not release the
intermediate object. Ex releasing
l_oInvoice does not release ProductObject.

Correct Usage:

 

Product l_oProdcut =
l_oInvoice.ProductObject;

l_oProdcut.GetProductCode();

System.Runtime.InteropServices.Marshal.ReleaseComObject(l_oInvoice);

System.Runtime.InteropServices.Marshal.ReleaseComObject(l_oProdcut);

 

Scenario 4: illegal memory access exception

 

When developing COM rich applications in .Net it is
possible that one faces illegal memory access exception,

“Attempted to read or write protected memory. This
is often an indication that other memory is corrupt”

 

Even though there may be many reasons for this
error including an error on the COM side itself, one of the possible reasons is
related to the treatment of COM objects in .NET.

 

Consider the following scenario of composition; we
have a COM collection object Invoices, which holds a collection of Invoice
objects.

 

Invoices object has a method reload which reloads
the data of the selected child from the database. Invoice object has a refresh
method which requests the reload.

 

Even though the example takes a collection to show
the concept the same is applicable to any kind of compositions with
dependencies.



Consider the below function
which takes Invoice number as the parameter and returns an Invoice
object.


 

Wrong
Usage:


 


public Invoice GetInvoices(string p_iInvoiceNumber)


{


  
Invoices l_oInvoices = new Invoices();


   Invoice l_oInvoiceToReturn;


 


   //Code to populate the Invoices from the
database


 


   for (int i = 0; i < l_oInvoices.count; i++)


   {


      if
(l_oInvoices[i].InvoiceNumber == p_iInvoiceNumber)


      {


        
l_oInvoiceToReturn = l_oInvoices[i];


        
break
;


      }


   }


  
Marshal
.ReleaseComObject(l_oInvoices);


   return l_oInvoiceToReturn;


}



 


The above function is returning the object, but it releases
the Invoices collection object. The using of the Invoice object will not create
any error as long as we do not try to refresh the data (which in this case is
assumed to be through the collection).

Correct usage:


 


public Invoice GetInvoices(string p_iInvoiceNumber, Invoices l_oInvoices)


{


   Invoice l_oInvoiceToReturn;


 


   for (int i = 0; i < l_oInvoices.count; i++)


   {


      if
(l_oInvoices[i].InvoiceNumber == p_iInvoiceNumber)


      {


       
l_oInvoiceToReturn = l_oInvoices[i];


       
break
;


      }


   }


   return l_oInvoiceToReturn;


}


 


The best way would be to pass the collection object to the
function and return the Invoice object. And let the calling function which wants
to use the Invoice object take care of the collection as well.


 



 


Conclusion


 


Debugging applications is as much a part of development as
implementing new code. Debugging COM interoperability applications could
sometimes be complex, especially when error messages are not specific. I have tried to share few
such scenarios that I came across during development hoping to save some time
for my co-developers who work on similar applications.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read