Feeds:
Posts
Comments

Posts Tagged ‘client’

In this post I address how .Net and COM types can be expressed and manipulated across architectural boundaries. And also, you are exposed to the key .NET-to-COM interoperability issues you are likely to encounter on a day-to-day basis. For example, you investigate a number of ways to build interoperability assemblies (including “primary” interop assemblies), examine core IDL to .NET data type mappings, and understand how key COM data structures (interfaces, co-classes, enumerations) are expressed in terms of .NET. Along the way, you take a more detailed look at the types contained in the System.Runtime.InteropServices namespace. As you might expect, the materials presented here work as the backbone for more advanced topics found in the remainder of the text.

 

A High-Level Overview of .NET-to-COM Interoperability

 

As you know; languages targeting the .NET runtime satisfy each pillar of object-oriented technology. For example, when you build an assembly using a given managed language, you are able to create classes that support any number of constructors, overloaded methods, and overridden members, and implement any optional interfaces. As well, the .NET platform makes use of a runtime garbage collector, which is responsible for freeing an object from the managed heap when it is no longer rooted in a given application.

 

In stark contrast, as you know COM types do not adhere to each and every pillar of OOP in the classic sense of the topic. For example, COM types are not created using class constructors, but rather using the IClassFactory interface. In addition, COM classes are not allowed to define over-loaded methods and cannot function as a base class to other COM types (as COM has no support for classical inheritance). As far as lifetime management of a co-class is concerned, COM does not make use of a garbage-collected heap, but employs a strict reference counting scheme provided courtesy of IUnknown.

 

Given the fact that COM and .NET types have so little in common, you may have deeprooted fears regarding interoperability issues. Ideally, a .NET client should be able to use a COM type with no concern for the mechanics of COM. For example, a managed client should be able to create the COM type using constructor semantics, derive new types from the COM wrapper class (given that .NET supports classic inheritance), and should not be required to obtain or release interface references (given that .NET does not demand the use of interface references). In a nutshell, as far as a .NET client is concerned, manipulating a COM type should look identical to the act of manipulating a native .NET type. For example:

 

// COM classes should appear as .NET types.

MyComClass c = new MyComClass();

c.SomeMethod(“Hello”, 12);

 

Obviously, this cannot be achieved unless you have an intermediary that stands between the .NET client and the existing COM type. In short, what we need is a proxy that is in charge of transparently handling .NET-to-COM communications. To be sure, whenever a .NET application makes use of a legacy COM type, a proxy is created by the .NET runtime. Formally, this proxy is termed a Runtime Callable Wrapper (RCW).

 

In a similar vein, a COM client should be able to make use of a .NET type without concern for the mechanics of .NET. For example, COM clients should be able to activate a .NET class using CoCreateInstance(); directly call the members of IUnknown, IDispatch, and IClassFactory; and should assume the type is maintaining an internal reference count. When unmanaged code communicates with managed .NET types, a different sort of proxy called a COM Callable Wrapper (CCW) is used to translate COM requests into terms of .NET. For the time being, let’s concentrate on the role of the RCW.

 

Understanding the Role of the RCW

 

The RCW is a .NET object that is in charge of marshaling calls between a managed unit of code and a given COM type. While a managed client is making calls to a given COM type, the RCW intercepts each invocation, translates each incoming argument into terms of IDL data types, and invokes the co-class method. Likewise, if the co-class returns any information to the caller (via [out] or [out, retval] IDL parameters) the RCW is responsible for translating the IDL type(s) into the appropriate .NET type(s). As you would hope, there is a fixed set of translation rules used to map between IDL and .NET atoms.

 

In addition to marshaling data types to and fro, the RCW also attempts to fool the .NET client into believing that it is communicating directly with a native .NET type. To do so, the RCW hides a number of low-level COM interfaces from view (IClassFactory, IUnknown, IDispatch, and so forth). Thus, rather than forcing the .NET client to make manual calls to CoCreateInstance(), the client is free to use the activation keyword of its code base (e.g., new, New, and so on). And rather than forcing the managed client to manually call QueryInterface(), AddRef(), or Release(), the client is able to perform simple casting operations to obtain a particular interface and is never required to release interface references.

 

It is important to understand that a single RCW exists for each co-class the client interacts with, regardless of how many interfaces have been obtained from the type. In this way, an RCW is able to correctly manage the identity and reference count of the COM class. For example, assume a C# Windows Forms application has created three co-classes residing in various COM servers. If this is the case, the runtime creates three RCW proxy types to facilitate the communication

 

A given RCW maintains a cache of interface pointers on the COM object it wraps and releases these references when the RCW is no longer used by the caller (and therefore garbage collected). In this way, the managed client is able to simply “new” the COM wrapper and is blissfully unaware of COM interface-based reference counting. Also, given that the RCW will not release the referenced interfaces until it is garbage collected, you can rest assured that a given co-class is alive as long as the .NET client is making use of the related RCW.

 

Understand, of course, that the RCW is responsible for more than simply mapping .NET types into COM atoms. The RCW is also responsible for mapping COM error objects (that is, IErrorInfo, ICreateErrorInfo) into managed exceptions. In this way, if a co-class throws a COM error, the .NET client is able to handle the problem using standard try, catch, and finally keywords. The RCW is also responsible for mapping COM event handling primitives (that is, IConnectionPointContainer, IConnectionPoint) into terms of managed delegates.

 

One question that may pop up at this point is “Where does an RCW come from in the first place?” As you will see, RCWs are .NET class types that are dynamically created by the runtime. The exact look and feel of an RCW will be based on the information contained within a related interop assembly. These assemblies contain metadata that is used specifically to bridge the gap between managed and unmanaged code. The good news is that you are not required to manually create interop assemblies by hand (though you could). Rather, you more typically make use of the tlbimp.exe tool that ships with the .NET SDK or the Visual Studio .NET IDE.

 

Read Full Post »