XP, Component Services and .NET

Registration & Interop

Register .NET Components with COM+

COM+ has two forms of registration, dynamic and manual. Both are practically effortless, although for the purposes of this sample, dynamic is satisfactory. Dynamic registration has a few requirements.

  1. Assembly must have strong name.
  2. Assembly may not be in the global assembly cache.
  3. Assembly must be used by managed (.NET) clients.
  4. Assembly activation type must be Library.

This may appear restrictive, but it actually includes many cases. I believe you are thinking, activation type must be Library. I do not create any Library COM+ components! Well, either with .NET, you will have the serviced component’s client on the same computer, or the client will be remoting to a surrogate application to access the COM+ components. Because of this, a pure .NET solution will be able to use dynamic registration most of the time.
Dynamic registration actually occurs the first time the client instantiates the serviced component, and only happens once for that version of the assembly. You will notice a lag the first time the component is accessed while the COM+ catalog is updated. The code is meant to track the number of objects that exist in memory and how many of those objects are active during or after a certain activity.

Notice there is indeed a pool of five objects and the just in time activation works after the initial call is made to the object. It seems that once the object is instantiated, it’s activated until a method is called. Once a method is called, the object is only activated during calls. This means you should hold off object instantiation until you are about to use object.

Migration Strategy

After you have decided to migrate part or all of an existing application to .NET, you will need to make a decision how finest to method the migration. This article introduces the horizontal and vertical methods to application migration. The issues you need to care about in forming a migration strategy are discussed simultaneously with some common migration scenarios.

Vertical and Horizontal Migration

Horizontal migration involves replacing a whole tier of your application. For example, you may choose to initially replace the ASP code within your Web-based presentation tier, or you may replace the COM code within your middle tier as the initial migration step. Vertical migration involves isolating and replacing a part of your application from side to side all n tiers.

Component Design

This article also presents guidelines relating to .NET/COM migration and interoperability to component design issues. When calling between .NET and COM environments through the interoperability layer, the CCW or the RCW (depending on the direction of the call) must translate the data on the call stack between the two environments. Certain data types (referred to as blittable types) do not require translation. Examples of blittable types include integers, longs, and floats (Singles and Doubles). Nonblittable types require translation by the wrappers.

A Visual Basic BSTR is an example of a nonblittable type. As you migrate your application to .NET, you should minimize the use of nonblittable types between managed and unmanaged code because the associated conversion overhead affects performance.

blittable data types

Most data types have a common representation in both managed and unmanaged memory and do not require special handling by the interop marshaler. These types are called blittable types because they do not require conversion when passed between managed and unmanaged code. Blittable types have a common representation across the interop boundary. Integer and floating-point types are blittable. Arrays and structures of blittable types are also blittable.

non blittable data types

Non-blittable types have different or ambiguous representations in managed and unmanaged languages. These types are called non-blittable types because they can require conversion when they are marshaled between managed and unmanaged code. For example, managed strings are non-blittable types because they can have several different unmanaged representations, some of which might require conversion. Strings, dates, and objects are examples on non-blittable types that are converted during the marshaling process.

Existing COM Components and Managed Clients

While you move your clients to .NET, take into account the interfaces exposed by your existing COM components. Typically, you will want to expose your existing COM components to your new .NET clients while leaving existing COM clients in place. Imagine, how you want to cover your existing components for both environments and care about future managed clients while you design your interfaces.

When migrate your components to .NET is the tlbimp utility to generate an automatic RCW for your application. This RCW, by default, exposes the same interfaces (and by definition the same properties and methods) as your existing component. In many cases, conventional COM-style interfaces exposed by the RCW will not be natural to use from managed code. Managed code developers will expect to be able to take advantage of such features as,

  • Parameterized constructors
  • Inheritance
  • Static methods

You should consider writing a custom wrapper class for your COM object to submit your managed clients these abilities and to create interfaces more suited to the managed code environment,. This wrapper class consumes the RCW for your COM components internally and delegates most calls to your existing COM components. Some calls may perform more complex data-type conversions such as mapping between ADO .NET datasets and ADO recordsets. Over time, you can move more and more of the functionality from the COM component to the wrapper without affecting your managed clients.
There are a number of factors to consider when deciding whether to use an RCW or a custom-managed wrapper class. Note the TlbImp utility, which is included in the SDK, can convert COM type libraries to .NET Framework metadata. Once the type library is converted to metadata, managed clients can call the COM type seamlessly. For ease of use, always provide type information in a type library.

Creating an RCW and exposing the existing interfaces may be an appropriate strategy if your components have a large numbers of clients who are accustomed to your existing object model,. For example, the object model in Microsoft Excel is widely used by Excel developers using Microsoft Visual Basic for Applications. The object model is highly structured and maps well to the features and user interface presented by Excel. Customers are very familiar with the existing object model and would require significant retraining if the object model were changed substantially. In this instance, using the standard RCW might be appropriate.

When writing custom-managed wrappers for COM interfaces that make heavy use of get and set properties remember that these interfaces—previously described as chatty interfaces—are used from managed code through an RCW, each property call crosses the interoperability boundary and incurs overhead. For a simple interface with minimal marshaling work, the interoperability overhead will be roughly 30 to 50 assembly instructions. This overhead is minimal for a method that performs a significant amount of work internally, but it would represent a large percentage overhead for a simple property access.

Consider writing a custom-managed wrapper and move the functionality represented by the chatty interface from the COM component to the wrapper if you expect the clients of your COM components to move to .NET soon, other, less chatty interfaces could be left implemented in the COM object and could be delegated to by the managed wrapper.

You will be able to minimize the interop call overhead and have a more natural interface to your object from managed code by repartitioning of code enables. An additional benefit of writing a custom-managed wrapper is that it allows you to move the remoting boundary as illustrated in Figure 8. This figure illustrates both a standard RCW and a custom-managed wrapper for a COM component. It shows how you can provide an interface more suited to managed clients, as well as how you can move the remoting boundary so that you can make .NET remoting calls.

Implement the class interface

The class interface, is not explicitly defined in managed code. It is an interface that exposes all public methods, properties, fields, and events that are explicitly exposed on the .NET object. This interface can be a dual or dispatch-only interface. The class interface receives the name of the .NET class itself, preceded by an underscore. For example, for class Mammals, the class interface is _Mammals. For derived classes, the class interface also renders all public methods, properties, and fields of the base class. The derived class also renders a class interface for each base class. For example, if class Mammals extends class MammalMainclass that itself extends System.Object, the .NET object exposes to COM clients three class interfaces named _Mammals, _MammalMainclass, and _Object.

The COM client can obtain a pointer to a class interface named Mammals, which is described in the type library that the Type Library Exporter (Tlbexp.exe) tool generates. If the Mammals class implemented one or more interfaces, the interfaces would appear under the coclass.

[odl, uuid(0000...0000), hidden, dual, nonextensible, oleautomation]
interface _Mammals : IDispatch
{
    [id(0x00000000), propget] HRESULT ToString([out, retval] BSTR* pRetVal);
    [id(0x60020001)] HRESULT Equals([in] VARIANT obj, [out, retval] VARIANT_BOOL* pRetVal);
    [id(0x60020002)] HRESULT GetHashCodes([out, retval] short* pRetVal);
    [id(0x60020003)] HRESULT GetType([out, retval] _Type** pRetVal);
    [id(0x6002000d)] HRESULT EatIt();
    [id(0x6002000e)] HRESULT GetBreathe();
    [id(0x6002000f)] HRESULT GoSleep();
}
[uuid(0000...0000)]
coclass Mammals
{
    [default] interface _Mammals;
}

Class interface generating is optional. If you do not select any other option COM interop generates a dispatch-only interface for each class you export to a type library as a default. You can prevent or modify the automatic creation of this interface by applying the ClassInterfaceAttribute to your class. Although the class interface can ease the task of exposing managed classes to COM, its uses are limited.

Use primary interop assemblies

Unlike other .NET assemblies, an interop assembly, contains no implementation code. Interop assemblies contain only the type definitions of types that are already implemented in COM components. It is from these type definitions that the CLR generates the RCW to allow managed code to bind to the types at compile time and provides information to the CLR about how the types should be marshaled at run time. Any number of assemblies can be generated for a COM type (using tlbimp is one method for generating an interop assembly). However, only one assembly is known as the Primary Interop Assembly (PIA). The PIA contains the software publisher's description of the types and is signed and authorized for use by that publisher. Because the PIA is signed by the software publisher and contains the PrimaryInteropAssembly attribute, it can easily be distinguished from other interop assemblies that define the same COM types. Please note that, the project system checks if a primary interop assembly for the specified type library exists. If it can found, then that assembly is used as the wrapper for the COM object's methods and properties. If not found, the behavior is the same as if "tlbimp" were specified.

Primary Interop Assemblies are important because they uniquely identify a type. Types defined within an interop assembly that was not provided by the component publisher are not compatible with types defined in the primary interop assemblies. For example, consider two developers in the same company who are writing managed code that will interoperate with an existing third-party supplied COM component. One developer acquires the PIA from the component publisher. The other developer generates his own interop assembly by running tlbimp on against the COM object's type library. Each developer's code works properly until one of the developers (or worse yet, a third developer or customer) tries to pass the object to the other developer's code. This results in a type mismatch exception; although they both represent the same COM object, the type checking functionality of the CLR recognizes the two assemblies as containing different types.

You should obtain the PIA for any COM components you use in your applications. Doing so helps to prevent type incompatibilities in code written by developers using the same COM object. You should also provide PIAs for any components you develop that might be used by others, especially if third party developers or customers will use these components in their applications.

You might also like...

Comments

About the author

John Godel United States

John H. GODEL has an experience more than 22 years in the area of software development. He is a software engineer and architect. His interests include object-oriented and distributed computin...

Interested in writing for us? Find out more.

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“You can stand on the shoulders of giants OR a big enough pile of dwarfs, works either way.”