Using COM Components from .NET Code


To use a COM object from .NET code, you first create a Runtime Callable Wrapper (RCW). You need the RCW because of several major differences between COM and .NET, which are summarized in the following table:

COM

.NET

Clients must manage the lifetimes of the COM objects they create.

The common language runtime (CLR) manages the lifetime of .NET objects.

Clients use QueryInterface or browse the object’s type information to find out whether a particular interface is supported.

Clients can use reflection to query an object.

COM objects are accessed through raw pointers and are therefore fixed in memory.

.NET objects are accessed through references and can be moved around by the CLR for performance reasons.

Wrapper classes are needed to bridge these differences so a COM object can appear as a .NET object and vice-versa.

How Do RCWs Work?

The wrapper takes the form of a proxy class that does all the work of creating and talking to the COM object so you can use COM objects just as if they were .NET objects. You can see how this works in the following diagram. The RCW does all the housekeeping by interacting with the Windows Registry, creating the object, forwarding calls to the object, and managing its lifetime. The primary goal of the RCW is to hide the complexity of COM objects from .NET programmers; in some cases, .NET programmers might not even know they are using a COM object.

click to expand

The wrapper class maintains a cache of interface pointers on the object it is using and releases these pointers when the object is no longer needed. The RCW itself is governed by the usual .NET garbage collection rules because it is a managed object.

Because data types often differ in the .NET and COM worlds, the RCW performs standard marshaling so both sides can use data types that are familiar to them. For example, when passing string data through an RCW, the .NET side works with String objects, but the COM side will probably use COM’s BSTR type; the RCW automatically converts between the two as necessary.

If you’ve used COM objects from C++, you’re aware that COM objects implement several standard interfaces—such as IUnknown and IDispatch—that COM client programmers have to know about. The RCW simplifies the process of using COM objects by automatically handling many of the standard interfaces, as listed in the following table.

Interface

Description

IUnknown

The RCW uses IUnknown for object identity checking, type coercion via QueryInterface, and lifetime management.

IDispatch

Used for late binding to COM objects using reflection (see below).

IErrorInfo

Used for providing error information.

IProvideClassInfo

If the COM object being wrapped implements this interface, the RCW uses it to provide better type identity.

IConnectionPoint and IConnectionPointContainer

If the COM object uses connection points, the RCW exposes them to .NET clients as delegate- style events.

IDispatchEx

If the COM object implements IDispatchEx, the RCW exposes and implements the .NET IExpando interface.

IEnumVARIANT

The RCW enables COM types that expose this interface to be treated as .NET collections.

Creating and Using RCWs

You can create RCW classes in two ways:

  • If you’re using Microsoft Visual Studio .NET, you can use a wizard to create the RCW for you.

  • If you’re compiling managed C++ code from the command line, you can use the .NET Framework tool called tlbimp.exe (for Type Library Importer) to read a COM type library and create a wrapper class based on the information it finds.

The following exercise shows you how to use Visual Studio .NET to create a wrapper for a COM object and then use the object.

Note

I’ve created a simple COM object for use in this exercise called TConverter. It implements the same simple temperature conversion functionality that I demonstrated in Chapter 23. You’ll find the source and executable for the Converter project, plus a ReadMe.txt file with directions for installing it, in this book’s sample files. Be sure TConverter is installed before starting this exercise.

  1. Create a new Visual C++ Console Application (.NET) project, and call it ComWrapper.

  2. Right-click on the project name in Solution Explorer, and choose Add Reference from the context menu.

  3. When the Add Reference dialog box appears, choose the COM tab. It will probably take a few seconds to populate the list box with details of the COM components registered on your system.

  4. Browse the list box to find the entry for the Converter component. Highlight this entry and then click Select, making sure a new entry appears in the Selected Components list box.

    click to expand

    Click OK to dismiss the dialog box. You will see that a new entry for Converter has been added to the project’s list of references.

  5. Open Windows Explorer and look in the project’s Debug directory. You will see that it contains a file called Interop.Converter.dll, which contains the RCW assembly. These files are always named Interop.XXX.dll, where XXX is the name of the COM component to which the RCW refers.

  6. Open ILDASM from the Visual Studio .NET 2003 Command Prompt window, and use it to examine Interop.Converter.dll.

    click to expand

    The shield-like symbol with the red top represents a namespace, so the namespace you need to import is Interop::Converter. You can see that the assembly contains three types. CTConverter and ITConverter represent the original COM coclass and interface definitions, respectively; their symbol is marked with an I to show that they are interfaces. CTConverterClass is a real type, so its symbol doesn’t contain the I. The RCW is produced by the tlbimp tool.

    To deduce the name of the wrapper class without using ILDASM, you take the name of the COM coclass and then put a C on the front and Class on the end.

  7. Add a using directive to your code to make it easier to reference the RCW:

    using namespace Interop::Converter;
  8. Add code to create a wrapper object, and use it to call methods on the COM object:

    int _tmain() { Console::WriteLine(S"COM Interop Sample"); // Create a COM object CTConverterClass* pt = new CTConverterClass(); // Call a conversion method and print the result double d = pt->ConvertC2F(27.0); Console::WriteLine(S"27C is {0}F", __box(d)); return 0; }

    Note how the wrapper is created just like any other managed object, and methods are called on it in exactly the same way as normal. There’s no way to tell from this code that you’re using a COM object, and the wrapper performs all the lifetime management for you.

Handling COM Errors

You know that COM methods return status and error information using 32-bit HRESULTs. The RCW converts all error HRESULTs into exceptions that you can catch in your code. The test Converter project returns an error if the conversion methods are passed any values less than -273C or -459 F because temperatures less than absolute zero have no meaning. Here’s the COM code:

STDMETHODIMP CTConverter::ConvertC2F(double dCelsius, double* dFahr) { if (dFahr == 0) return E_POINTER; // Temperatures below -273C are meaningless... if (dCelsius < -273.0) return E_INVALIDARG; *dFahr = (9/5.0 * dCelsius) + 32; return S_OK; }

This code might return two error HRESULTs: the first, E_POINTER, occurs if the pointer to the result variable is null, which won’t happen when called by the RCW. The second, E_INVALIDARG, occurs if an invalid temperature is passed. These are converted to exceptions by the RCW, and as usual, you need to catch them to prevent your program from terminating. Here’s what you’ll see on the Console if you pass an invalid temperature:

Unhandled Exception: System.ArgumentException: The parameter is incorrect. at Converter.CTConverterClass.ConvertC2F(Double dCelsius) at main() in c:\code\comwrapper\comwrapper.cpp:line 19

You can handle this by adding a try/catch block to the code in the _tmain function:

try { double d = pt->ConvertC2F(-280.0); Console::WriteLine("-280C is {0}F", __box(d)); } catch(Exception* pe) { Console::WriteLine("Exception from COM object: {0}", pe->Message); }

Late Binding to COM Objects

RCWs implement early binding connections to COM objects because when you have a type library, you have all the details of what the COM object can do available to you at compile time. If you want to use a COM object that implements IDispatch, you can also call it at run time, but the process is a little more complex.

The following exercise shows how to use the Converter object with late binding. This COM object was created with a dual interface, so it can be accessed via both early binding and late binding.

  1. Create a new Visual C++ Console Application (.NET) project called LateBind.

  2. Add code to _tmain to get a Type object that represents the COM component. (Consult Chapter 26 for more details on the Type class and its uses.)

    // Get a type representing the COM object Type* t = Type::GetTypeFromProgID(S"Converter.TConverter"); if (t == 0) { Console::WriteLine(S"Error getting type for TConverter"); return -1; } Console::WriteLine(S"Got type for TConverter");

    The GetTypeFromProgID static method takes a COM progID as a string and creates a Type object to represent the coclass. If there is a problem creating the Type object because the progID can’t be found or because of some other registry-related problem, a null pointer is returned. Overloads of this function let you specify that an exception be thrown instead of getting a null pointer, if that suits your code better.

  3. Use the System::Activator class to create the COM object for you:

    // Use System::Activator to create an instance Object* pObj = Activator::CreateInstance(t);

    The Activator class creates instances of local or remote objects for you. The reference returned is a general object reference; you don’t need to cast it to any specific type because this will be taken care of for you later.

  4. Build the parameter list before you call a conversion method on the object. This takes the form of an array of Objects:

    // Make up the argument list Object* pArgs[] = { __box(27.0) };

    Here, the array contains only one value—the temperature to be converted. As you know by now, built-in types have to be converted before they can be used as objects, so you need to box the value.

  5. Call the conversion method dynamically, using the InvokeMember method of the Type class:

    // Invoke the method try { Object* pa = t->InvokeMember(S"ConvertC2F", Reflection::BindingFlags::InvokeMethod, 0, pObj, pArgs); double d = Convert::ToDouble(pa); Console::WriteLine(S"27C is {0}F", __box(d)); } catch(Exception* pe) { Console::WriteLine(S"Exception from Invoke: ", pe->Message); } 

    InvokeMember, as its name implies, dynamically invokes a member of an object. The arguments supplied to the function are the name of the member to be invoked, the type of operation (in this case, you’re invoking a method rather than accessing a property or field), a pointer to a Binder object (which you’re not using), a pointer to the object on which the operation is to be invoked, and a pointer to the argument array.

    If the call works, you’ll be passed back an Object reference representing the result, which is then converted to the appropriate type using one of the static methods of the Convert class.

Using ActiveX Controls in Windows Forms Projects

Any serious COM application makes use of one or more ActiveX controls, and GUI applications commonly use many third-party components that have been implemented as ActiveX controls.

Note

The current definition of an ActiveX control is any COM object that implements at least IUnknown and that handles its own registry entries on installation and removal. This includes just about any modern COM object, so it isn’t a very useful definition. In this section, I’m talking about ActiveX controls in the original sense, meaning components (often graphical) that can be used in a form-based programming environment.

Traditional ActiveX controls are among the most complex COM objects in common use, and only an expert COM programmer would try to write an ActiveX control from scratch. Fortunately, GUI programming environments such as Visual Studio usually hide the complexity of ActiveX controls so you can easily use them in your projects.

ActiveX controls are accessed via an RCW just like any COM object, but because they are usually used in Windows Forms projects, the System::Windows::Forms namespace contains a special class, AxHost, that forms the basis of RCWs used to talk to ActiveX controls. AxHost also takes care of interacting with the development environment so the control wrapper can appear in the toolbox, be dragged onto forms, and have its properties edited in the normal way.

As with plain RCWs, there are two ways to create an RCW for an ActiveX control:

  • If you’re using Visual Studio .NET, you can simply add a reference to a COM object as if it were a .NET object. Visual Studio automatically generates an appropriate wrapper class derived from AxHost for you, and it adds an icon to the Toolbox so you can use it just like any other .NET Windows Forms control.

  • If you’re compiling managed C++ from the command line, you can use the .NET Framework tool called AxImp.exe to generate the RCW for you.

The exercise that follows shows how to generate an RCW for an ActiveX control and use it in a Windows Forms project. The control I typically use for this exercise is the Microsoft Calendar control that is installed with Microsoft Office; it is widely available and easy to program, and it is easy to see whether it appears correctly on the form.

  1. Create a new Visual C++ Windows Forms Application (.NET) project, and call it UseActiveX.

  2. Display the Toolbox. If it isn’t already visible, use the Toolbox item from the View menu or press Ctrl+Alt+X to display it.

  3. Right-click anywhere on the Toolbox, and choose Add/Remove Items from the context menu.

  4. When the Customize Toolbox dialog appears, select the COM Components tab. Locate the entry for the Calendar ActiveX control, and make sure the check box is selected. Press OK to dismiss the dialog box, and add the control to the Toolbox.

    click to expand

    You will now find that an entry for the Calendar control has been added to the bottom of the Windows Forms controls in the Toolbox.

  5. Drag a Calendar control from the Toolbox onto the form. If you look at the property editor, you can see that the imported control exposes all the properties of the underlying ActiveX control. You’ll also find that two new interop DLLs have appeared in the project Debug directory, called Interop.MSACAL.dll and AxInterop.MSACAL.dll. The first of these is the plain RCW that lets you interface to the COM types defined in the OCX, while the second holds the wrapper class derived from AxHost that lets you use the control on a form.

  6. Build and run the project, and you’ll see a form that displays a
    Calendar control, like this:

    click to expand

Calling Control Methods

You can call methods on the imported ActiveX control just as you would on any other .NET control. If you aren’t using Visual Studio .NET, the easiest way to see what you can call is to use ILDASM to list the methods exposed by the wrapper. The following short exercise shows you how to interact with the Calendar component:

  1. Continue with the same project, but increase the size of the form to 400,400.

  2. Add two buttons to the form, for stepping the calendar to the next and the previous month.

  3. Add Click event handler functions for the two buttons, to call the NextMonth and PreviousMonth methods of the calendar control:

    private: System::Void nextBtn_Click(System::Object * sender, System::EventArgs * e) { axCalendar1->NextMonth(); } private: System::Void prevBtn_Click(System::Object * sender, System::EventArgs * e) { axCalendar1->PreviousMonth(); }

    You can find the name of the Calendar control instance by looking at the control data members that have been added to the Form1 class or by using the property editor.

  4. Build and run the project, and you’ll be able to step the Calendar through the months.




Microsoft Visual C++  .NET(c) Step by Step
Microsoft Visual C++ .NET(c) Step by Step
ISBN: 735615675
EAN: N/A
Year: 2003
Pages: 208

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net