Head-Spinning Continued: COM Interop

From Kate Gregory's Codeguru column, "Using Visual C++ .NET".

-->

In my last column, I set the scene for reusing some old C++ code from new Managed C++ code. The reuse technique that's received the most publicity and coverage is COM Interop. In essence, any COM component can appear to your managed code to be a managed object running in the .NET Framework. In this column, I'll show you how to convert an ordinary unmanaged class to a COM component, and then use that COM component from both unmanaged and managed code.

Creating a COM Component

When you think COM and C++, I hope you think ATL. The Active Template Library makes writing COM components much simpler than it once was. You create an ATL project, add an object to it, add an interface (or several) to the object, and code the methods. If your "legacy library" was many classes with many functions in each, you would probably start by designing the interfaces, and then write your COM component as a wrapper around the real classes: The wrapper would hold an instance of one or more of the real classes, and use their methods to provide the services offered by your interfaces. Because the legacy library I'm using in this series of columns is just a single class (ArithmeticClass) with a single method (Add), there's not much design involved. I'm actually going to create the method all over again; then paste in the method body from the original class. (I presented the legacy class in my last column.)

In Visual Studio, I created a new project (choosing ATL Project as the type) called COMArithmetic. I accepted all the defaults on the new project wizard. Then, I added a class by right-clicking the project in Solution Explorer and choosing Add, Add Class. I chose an ATL Simple Object and on the ATL Simple Object Wizard I named it ArithmeticClass. (It's a good idea to use a different name from the legacy class, whether you're wrapping it or using copy-and-paste to duplicate it in ATL.) Then, in ClassView I expanded the COMArithmetic node to reveal IArithmeticClass, the interface that was generated for me, and I added a function to the interface by right-clicking the interface and choosing Add, Add Method. Here's how the Add Method Wizard looked just before I hit Finish:



Click here for a larger image.

Then, because Add is so simple, I just typed in the body. Here's how it looks:

STDMETHODIMP CArithmeticClass::Add(DOUBLE num1, DOUBLE num2,
                                   DOUBLE* answer)
{
  *answer = num1 + num2;

  return S_OK;
}

Building the project will create and register the COM component. Now it's ready to be used by other applications.

Using the COM Component from Unmanaged Code

One of the reasons you might package your legacy code into a COM component is so that it can still be used from other legacy unmanaged applications. Here's a very simple console application that uses the new COM component:

#import "..\ComArithmetic\Debug\ComArithmetic.dll" no_namespace

int _tmain(int argc, _TCHAR* argv[])
{
  ::CoInitialize(NULL);
  //braces for scope only
  {
    IArithmeticClassPtr arith("ComArithmetic.ArithmeticClass");
    double answer = arith->Add(1,3);
    cout << "1 + 3 is " << answer << endl;
    cin.get();
  }
  ::CoUninitialize();
  return 0;
}

This code is made simpler (honestly) by the #import, which sets up a smart pointer class, IArithmeticClassPtr, that wraps the component. The extra set of braces ensure that the instance of this smart pointer is destructed before CoUninitilalize() runs. The heart of this code is the call to the Add method, made through the overloaded -> operator of the smart pointer (which, of course, isn't really a pointer.) Althoug this code is full of COM "goo," you can see that the methods exposed by the COM component are available to you from unmanaged code without too much trouble.

Using the COM Component from Managed Code

It's actually easier to work with a COM component from managed code than from unmanaged code. The key is a Runtime Callable Wrapper (RCW), a piece of code that looks like a COM component on the outside, but inside holds classids and progids and the like to help it divert all calls from the runtime into the real COM component for processing. The RCW handles QueryInterface, AddRef, and Release, marshals between COM and .NET types, converts HRESULTs into .NET exceptions, and generally makes life much simpler for the developer. To create one, just add use the COM tab of the Add Reference dialog.

I created a new .NET Console application called ManagedTestCOMArithmetic, then right-clicked References in Solution Explorer and chose Add Reference. (This is new in Visual Studio .NET 2003.) On the COM tab, I scrolled down until I reached COMArithmetic, then clicked Select and OK. This creates the Runtime Callable Wrapper for the COM Component. The name of the resulting managed class is built from the name of the COM class (CArithmeticClass) with Class stuck on the end, giving me the rather strange CArithmeticClassClass to work with. Here's how the main looks:

double answer;
Interop::COMArithmetic::CArithmeticClassClass* arith = 
         new Interop::COMArithmetic::CArithmeticClassClass();
answer = arith->Add(2,3);
Console::Write("2 + 3 is ");
Console::WriteLine(__box(answer));
return 0;

There are really only two wrinkles in this simple little piece of code. First, how did I know that the managed class I create with new is called Interop::COMArithmetic::CArithmeticClassClass? I right-clicked on the reference I just added, and chose Open, to bring up the Object Browser.



Click here for a larger image.

This tells you all you need to know about the wrapper, including the full name of the namespace and the class, and even confirms that the method is called Add().

The second wrinkle is that Add() returns a double. To pass this fundamental type to WriteLine, you need to box it (as discussed in my third column, Boxing Value Types in Managed C++.) You can count on having to deal with the managed/unmanaged data issue no matter what legacy code you wrap up to reuse in this way: It's going to return unmanaged data and if you want to give that data to a method from the Base Class Libraries, you're going to have some conversion to do. If you're lucky, it will be as simple as boxing it. It may require some thought and design effort.

Is COM Interop the Right Choice for You?

If the legacy code you want to reuse is already a COM component, by all means use COM Interop to get to it. But, if it's just a collection of classes, think twice before turning it into a COM component just so that you can reach it from a managed application. Of all the techniques I'm going to show you for reusing legacy code, COM Interop is the slowest. It's a fair amount of work to create the wrapper component, and to add code to other legacy applications to use the component through COM instead of directly.

If you have an existing COM component that you need to use from managed code, this is a great first approach. But be sure to do some performance measurement right from the start. If the performance penalty imposed by COM Interop is too high for your application, you'll have to think about some refactoring. You could move most of the code into a standalone .LIB and have both the COM component and the new managed code use the library. That would give you the best performance while still exposing a COM component for your legacy code. Or, you could move your whole component into managed code, and expose it using .NET Interop (which makes a .NET managed class look like a COM component) to the legacy code. It all depends on where you can afford the performance hit.

How would managed code use the library? What other ways are there to reuse code beside COM Interop? Stay tuned; I'll be covering other reuse techniques in the columns to come.

About the Author

Kate Gregory is a founding partner of Gregory Consulting Limited (www.gregcons.com). In January 2002, she was appointed MSDN Regional Director for Toronto, Canada. Her experience with C++ stretches back to before Visual C++ existed. She is a well-known speaker and lecturer at colleges and Microsoft events on subjects such as .NET, Visual Studio, XML, UML, C++, Java, and the Internet. Kate and her colleagues at Gregory Consulting specialize in combining software develoment with Web site development to create active sites. They build quality custom and off-the-shelf software components for Web pages and other applications. Kate is the author of numerous books for Que, including Special Edition Using Visual C++ .NET.




Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: October 29, 2014 It's well understood how critical version control is for code. However, its importance to DevOps isn't always recognized. The 2014 DevOps Survey of Practice shows that one of the key predictors of DevOps success is putting all production environment artifacts into version control. In this webcast, Gene Kim discusses these survey findings and shares woeful tales of artifact management gone wrong! Gene also shares examples of how high-performing DevOps …

  • Today's agile organizations pose operations teams with a tremendous challenge: to deploy new releases to production immediately after development and testing is completed. To ensure that applications are deployed successfully, an automatic and transparent process is required. We refer to this process as Zero Touch Deployment™. This white paper reviews two approaches to Zero Touch Deployment--a script-based solution and a release automation platform. The article discusses how each can solve the key …

Most Popular Programming Stories

More for Developers

RSS Feeds