House of COM: Is COM Dead?

We were unable to locate this content in de-de.

Here is the same content in en-us.

This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.
MSDN Magazine
Is COM Dead?
Don Box
A
s I write this, about a month has passed since the Orlando PDC in July 2000. This PDC was easily the best Microsoft conference since the Microsoft® Transaction Server (MTS) PDC in Long Beach in 1996. Both events were significant milestones for Microsoft that laid out the blueprints for fundamentally new platforms and programming models. However, the reason that both PDCs are so memorable for me personally is because I spent a great deal of time at both answering the question: "So does this mean that COM is dead?" My answer at the Long Beach PDC was a resounding no. My answer at this PDC was (to paraphrase Bill Clinton): "It depends on what your definition of COM is."
      COM is many things to many people. To me, COM is a programming model based on integrating components based on type. Period. This was COM's primary contribution to the field of component software, and that contribution has changed the way millions of programmers build systems today.

Type Information Limitations

      Programmers who use COM load code by creating new instances of concrete types. Once the code is loaded, programmers resolve entry points by coercing object references to new abstract types. To facilitate the former, COM provides CoCreateInstance as a type-oriented alternative to the file-oriented LoadLibrary API call. To facilitate the latter, COM provides the QueryInterface method as a type-oriented alternative to the symbol-oriented GetProcAddress API call. To see this programming model in all of its gory detail, look one last time at the COM and C++ code in Figure 1. Notice that no calls to LoadLibrary or GetProcAddress are anywhere to be found, yet this code loads an external component and resolves entry points as if these two functions had been called.
      The key to understanding the future of COM is buried in the code in Figure 1. This code illustrates the tension between the type system of COM and the type system of the hosting language (in this case, C++). Notice that everywhere an object reference is returned to the caller, a UUID (in this example, IID_IAntique or IID_ICar) must be passed in tandem with the object reference. This is because the type identifier of the host language (std::type_info in the case of C++) isn't compatible with the type identifier format of COM.
      Over the years, the C++ team at Microsoft introduced several technologies to bridge the C++ and COM type systems, the most important (yet subtle) of which was the__uuidof and __declspec(uuid) extensions. These extensions allowed the MIDL compiler to associate the COM type identifier with the symbolic type name in C++. When using __uuidof, the code becomes much more precise, as shown in Figure 2.
      Note that unlike the code in Figure 1, if the type of pAntique were to be modified, the proper interface identifier would always be used in the call to CoCreateInstance, since the__uuidof operator always gets the appropriate IID for pAntique.
      Despite the use of __uuidof, the code in Figure 2 was forced to make extensive use of (void **) casts. These casts are ubiquitous in C++ and COM programming and are necessary, because to make COM work with C++ you need to disable the C++ type system long enough to translate the COM object reference back into C++. In contrast, the COM integration in Microsoft's virtual machine (VM) for Java allowed you to write the following:
IAntique antique = new Pinto();
ICar car = (ICar)antique;
car.AvoidFuelTankCollision();
antique.Appreciate();
      The net result was the same as the C++ code in Figure 2. Both examples load a component based on its type, not its file name. Both examples resolve entry points into the component using type operations, not symbolic entry points. The primary difference is that the Microsoft VM for Java is able to do a great job of bridging the type system of Java with the type system of COM, so programmers don't have to do it by hand. This is precisely the motivation for the new platformâ€"to provide a universal runtime for component integration that any compiler, tool, or service can then take advantage of.
      While the PDC in Orlando was framed as the Microsoft .NET PDC, the most pervasive platform shift for COM users is the common language runtime (CLR). The CLR is a new implementation of the ideas first popularized by the COM programming model. In the CLR, programmers load components based on type, not file name; they resolve entry points using type coercion operations, not symbolic entry points. The CLR programming model is no different than the COM programming model in this fundamental respect. So, if the CLR programming model is so similar to the COM programming model, why do you need the CLR? The answer lies in the implementation.
      In my July 1998 column in MSJ, I listed the top five problems with COM as it sat in 1998. Virtually all of the problems were rooted in the sorry state of COM type information. In classic COM, there are three types of information formats: IDL, type libraries, and the MIDL-generated /Oicf strings that are embedded inside of proxy/stub DLLs. None of these three formats were considered standard, and it was possible to put information in one format that had no representation in the other two. For this reason, there was no standard way to describe types in COM, which made life difficult both for the developers who build COM infrastructure as well as the average COM programmer churning out component applications. Over time, type libraries have become the de facto standard format, but it is possible to represent things in type libraries that cannot be used from IDL.
      In addition to the lack of a standard type information format, COM type information addressed only a subset of a component's types. COM type libraries describe only the types that are exported from a component. Information about exported types allows COM-aware infrastructure and tools to provide features like IntelliSense® in Visual Basic® and the COM+ declarative service architecture. That's the good news. Unfortunately, COM made no attempt to advertise which external components a component depends on to function properly. While COM did a great job at rendering dumpbin.exe /exports obsolete, it made no attempt to address the dumpbin.exe /imports problem. The lack of dependency information makes it difficult for the system to determine a priori which DLLs (and which versions) must be installed for the component to function properly.
      Another critical limitation of COM type information is that no information is available about the private types that are used internally within the component. While COM advocates have long touted COM's neutrality with respect to object representations, this Switzerland-like stance has made certain services impossible, such as automatic serialization, lifetime tracking of object graphs, or automatic pre/post-condition assertions. Most developers who have been doing serious work with COM have long wanted perfect type information. That is precisely what the common language runtime provides.

CLR Enhancements

      The primary feature of the CLR is ubiquitous, extensible, high-fidelity type information. In the CLR, all typesâ€"not just the types that are exported from a componentâ€"have runtime type descriptions. In the CLR, you can walk up to any object and discover every aspect of its type definition, including its representation. This is a fundamental departure from classic COM, where public interfaces were discoverable, but object representations were opaque.
      The CLR programming model is type-centric. Like COM before it, the CLR supplants the underlying loader with its own type-based loader. In addition, the CLR supplants the underlying symbol table with its own type-based resolution mechanism. In these respects, the CLR is functionally identical to COM. But what's more interesting is how the CLR improves the component development experience beyond what was possible with COM alone. Here are my 11 favorite enhancements.
Components are first-class citizens In classic COM, the term "component" was overloaded; it could refer to an object, class, or a DLL that exports one or more classes (I prefer the latter definition). The CLR formalizes the notion of component as an assembly. An assembly is an indivisible set of types that are deployed as a unit. Assemblies are logical collections of types whose implementation code may be spread across multiple modules (DLLs or EXEs). The name of the assembly scopes the contained type names, removing the need for per-type UUIDs. Provided that no two assemblies share a name, there will never be a type name collision across components.
      For one-off components used by a single application, the file names of the containing assembly are sufficiently unique. For components that will be shared by multiple applications, CLR assemblies can be assigned strong names. A strongly named assembly has a 128-byte public key that identifies the developer of the component. When a client program is linked against a strongly named assembly, a 64-bit hash of the public key is stored in the client program's metadata. At runtime, the assembly's public key is compared to the 64-bit hash that was stored in the client's metadata, ensuring that the correct assembly was loaded.
      In COM, every type carries a 128-bit UUID to ensure uniqueness. In the CLR, every assembly carries a 128-byte public key, which, when combined with locally unique symbolic type names, provides global uniqueness of type identifiers. The net effect is the same: types have globally unique identifiers.
Only one metadata interchange format exists As mentioned earlier, COM type information was exchanged either in text (IDL) or binary (TLB) form. In contrast, CLR type information is always interchanged in a single, documented, binary form. All CLR-aware tools and compilers emit and consume metadata in this format. So to define a set of interfaces, the developer can use their native programming language (and compiler) to generate type descriptions, rather than requiring one syntax (IDL) to describe types and another (such as C++ or Visual Basic) to implement them.
      For example, consider the following COM IDL:
[ uuid(ABBAABBA-ABBA-ABBA-ABBA-ABBAABBAABBA) ]
library MyLib {
  importlib("stdole32.tlb");
  [ uuid(87653090-D0D0-D0D0-D0D0-18121962FADE) ]
  interface ICalculator : IUnknown {
    HRESULT Add([in] double n, 
                [in, out] VARIANT_BOOL *round,
                [out] VARIANT_BOOL *overflow,
                [out, retval] double *sum);
  }
}
The equivalent CLR type could be expressed in C# as follows:
namespace MyLib {
  interface ICalculator {
    double Add(double n, 
               ref bool round, 
               out bool overflow);
  }
}
Assuming that this source file was compiled using the C# compiler as follows
csc.exe /t:library /out:mylib.dll mylib.cs
you could easily import the interface into Visual Basic using the /r: compiler switch:
vbc.exe /r:mylib.dll program.vb
The CLR doesn't eliminate the need to define types; rather, it simply allows developers to define types in the language of their choice.
      In addition to defining the standard binary format for type descriptions, the CLR provides library support for both reading and writing type descriptions. Figure 3 contains a program that emits an assembly containing the type definition for the MyLib.ICalculator interface shown previously. The type definition generated from this program is indistinguishable from one that was produced by the C# (or Visual Basic, or C++, or Perl, or Python) compiler.
Metadata is mandatory In COM, it was possible to define private interfaces in C++ that were never described in IDL or in a type library. The primary reason for doing this was to provide a backdoor into your object that wasn't a documented part of your component. Unfortunately, this technique broke down if you wanted cross-context, cross-apartment, cross-process, or cross-host access, as the system could not construct an interface marshaler without type information.
      In the CLR, all types must be documented via type information. This includes private types that are not intended for intercomponent use. To support component-private types, CLR metadata allows types (and type members) to be marked as accessible only within the defining assembly. For example, the following interface is only visible within the assembly in which it is defined:
internal interface IBob {
  void hibob();
}
In contrast, the following interface is visible to any assembly:
public interface IBob {
  void hibob();
}
Metadata is fully extensible COM type information was extensible via the IDL custom attribute and its corresponding TLB format. COM custom attributes associated a UUID/VARIANT pair with a library, interface, coclass, method, parameter, struct, or field. COM custom attributes were used sparingly over the years, primarily by MTS, which allowed the initial value of a class's transaction attribute to be set using well-known custom attributes (defined in mtxattr.h and exposed via the Transaction property in Visual Basic). Unfortunately, Visual Basic didn't offer a way to define or use additional custom attributes, so this feature was useless to the majority of COM developers.
      CLR type information is fully extensible from any language. In CLR, custom attributes are simply serialized constructor calls. Each language defines its own syntax for applying attributes. In C#, you simply insert the constructor call in brackets prior to the definition of the target:
[ Color("Red") ]
class MyClass { }
In Visual Basic .NET, you simply insert the constructor call in < > characters before the name of the target:
Class <Color("Red")> MyClass
End Class
In both cases, the resultant metadata will indicate that the color of MyClass is Red.
      To define new custom attributes, you need to define a new class that extends System.Attribute and provide a public constructor:
using System;
[ AttributeUsage(AttributeTargets.All) ]
public class ColorAttribute : Attribute {
  public String color;
  public ColorAttribute(string c) { color = c;}
}
The AttributeUsage attribute indicates what types of metadata constructs (class, method, property, and so on) this attribute is applicable to. Note that when the attribute class name ends with Attribute, the attribute may be used either with or without that suffix. For example, the following two uses are identical, although the latter form is rarely used:
[ Color("Red") ] class MyClass { }
[ ColorAttribute("Red") ] class MyClass { }
      Custom attributes are made available at runtime via reflection. The code in Figure 4 determines which color (if any) a given class has been given. Attributes provide a clean, type-centric extensibility model to the CLR type description format.
Dynamic invocation is free Because type information is high-fidelity and ubiquitous, the runtime can dynamically invoke any method on any object without object intervention. This means that all CLR objects are automatically scriptable using typeless programming languages.
Physical layout is opaque The CLR is based on perfect type information. That means that no type is left undescribed, down to the types and names of a type's data members. Paradoxically, the physical layout in memory of a given type and its instances is completely opaque. The offsets, sizes, and alignment of data members are opaque, as is the layout of vtbl/vptrs used to dispatch method calls. If a CLR object needs to be exported outside of the runtime, the CLR creates a COM-callable wrapper (CCW) that thunks the classic COM calling convention based on IUnknown and __stdcall into the runtime's internal calling conventions. For the application developer looking to be productive, this is great news, as the runtime simply does the right thing based on metadata. For low-level bit-heads, this means that new techniques are needed for dynamically generating new types and for intercepting access to existing types. System.Reflection.Emit provides the ability to generate new types; System.Runtime.Remoting provides the ability to intercept calls to existing types.
Singly rooted type hierarchy The COM type system had two roots. IUnknown was the root type for object references, and VARIANT was the root type for all values. In the CLR type system, both IUnknown and VARIANT are gone. Instead, all types extend System.Object. Yes, this means that primitive types like int and double extend System.Object. The functionality of the VARTYPE field has been subsumed by the System.Object.GetType method. So the C++ code at the top of Figure 5 could be rewritten as the C# code at the bottom of Figure 5. Note that the C# code has operator tests for type-compatibility and that the cast operator does the coercion both for object references and for value types.
Classes can have public members In COM, classes were named implementations of one or more interfaces. Period. Classes were not allowed to have methods or properties that were not scoped by some interface. In CLR, classes are allowed to have public members. This allows methods to enforce the use of a particular implementation of an interface as well as enabling one-off programs to eschew interfaces entirely.
Multiple inheritance of interfaces is now supported COM interfaces only support single inheritance. The primary reason for this was to maintain cross-compiler binary compatibility for C++ users. The CLR type system allows interfaces to extend more than one interface. This allows a new interface contract to require compliance with more than one interface. For example, consider the following interface suite:
public interface IMachine { }
public interface IAsset { }
public interface IRobot : IAsset, IMachine { }
The IRobot type indicates that to be a robot, an object must also be a machine and an asset. In classic COM, there was no way to express this constraint without resorting to runtime assertions.
Delegates provide an alternative form of component glue In COM, all method calls were invoked on object references that belonged to some interface type. To design an extensibility hook, you typically defined a new interface and required extensions to implement that interface. Consider the following C# type that uses an interface for extensibility:
public interface IHook {
  void DoHookWork();
}
public class MyClass
{
  IHook hook;
  public void RegisterHook(IHook h) { hook = h; }
  public void f() {
    if (hook != null)
      hook.DoHookWork();
  }
}
To write a hook for MyClass, you simply need to implement the IHook interface
public class MyHook : IHook {
  public void DoHookWork() {
    System.Console.WriteLine("Hook was called");
  }
}
and register an instance via RegisterHook:
MyClass mc = new MyClass();
mc.RegisterHook(new MyHook());
mc.f(); // calls MyHook.DoHookWork()
As shown here, this idiom carries forward into the CLR. However, the CLR provides an alternative mechanism called delegates.
      CLR delegates are used to bind an object's method call to a variable. They are a cross between function pointers in C and pointer-to-members in C++. Delegates are typically used as duct tape to dispatch a call to some runtime-specified method/object combination. Functionally, delegates are similar to interfaces that only have one method, with the distinction that the target type only needs to have a method whose signature matches the signature of the delegate.
      Consider the following delegate-based variation on the previous example:
public delegate void Hook();
public class MyClassEx
{
  Hook hook;
  public void RegisterHook(Hook h) { hook = h; }
  public void f() {
    if (hook != null)
      hook(); // invoke hook
  }
}
Note that instead of an interface, a simple delegate type was defined. Also note that the invocation of the hook uses a syntax similar to C function pointers. Under the hood, the C# compiler generates a call to the delegate type's Invoke method, which in turn invokes a method on the target object.
      To write a hook for MyClassEx, you simply need to implement a method whose signature matches the Hook delegate's signature:
public class MyHookEx {
  public void AnyNameIWant() {
    System.Console.WriteLine("Hook was called");
  }
}
Note that this type doesn't have any explicit references to the delegate type Hook. Rather, it simply has a method whose signature matches the Hook signature, which makes this type a candidate for registration as a hook for MyClassEx. To register the hook, you simply need to instantiate a new delegate based on the Hook delegate type:
MyClassEx mc = new MyClassEx();
mc.RegisterHook(new Hook(MyHookEx.AnyNameIWant));
mc.f(); // calls MyHookEx.AnyNameIWant()
Note the somewhat unusual syntax for initializing the delegate. Under the hood, the C# compiler generates code to initialize the new delegate object with the metadata token for the designated method.
Aspect-oriented programming is fully supported MTS and COM+ introduced aspect-oriented programming to the masses by allowing developers to move domain-neutral aspects of their programs out of the source code and into declarative attributes. MTS and COM+ also introduced the notion of context to provide an execution scope for an object. Contexts subdivided processes and contained an ordered collection of named context properties, which were controlled by class attributes such as Synchronization, ThreadingModel, Transaction, and so on. The CLR brings this concept forward and cleans up the implementation considerably.
      In MTS and COM+, the set of class attributes and context properties was fixed. In the CLR, anyone can define a new class attribute that contributes new properties to an object's context. This allows the average developer to define services that are expressed via attributes, context properties, and interception.
      In COM+, object references were scoped by a context, and the Global Interface Table (GIT) was needed to share object references in global variables. In the CLR, object references are scoped by an AppDomain (the CLR equivalent of a process), and object references can be freely shared in global variables with no marshaling required.
      In COM+, all objects are pinned to the context in which they were initialized and marshal by reference to all other contexts by default. As a result, even objects that don't care about services such as transactions or declarative security get pinned to a particular context inside a process. To avoid this, shared objects often aggregate the freethreaded marshaler (FTM), which make them context-agile within a process but marshal by reference across process boundaries. Objects that want cross-process access to happen via a copy of the object rather than via a proxy typically implement IMarshal to provide marshal-by-value semantics.
      In the CLR, the default is marshal by value across AppDomains and context-agile within AppDomains, as shown in Figure 6. This means that by default, an object will never get a proxy. Rather, it will be accessed directly within its original AppDomain, and cross-AppDomain access will work on cloned copies of the object. As shown in Figure 7, classes that extend System.MarshalByRefObject are context-agile within AppDomains, but marshal by reference across AppDomains (this is roughly equivalent to aggregating the FTM in COM+). Objects that extend System.ContextBoundObject are pinned to the context in which they are initialized (see Figure 8), exactly like the default behavior in COM+. These three configurations are achieved with no explicit coding, but rather just by changing the base type of a class.

Figure 6 A CLR Object
Figure 6 A CLR Object

Figure 7 A CLR MarshalByRefObject
Figure 7 A CLR MarshalByRefObject

Figure 8 A CLR ContextBoundObject
Figure 8 A CLR ContextBoundObject

So, Is COM Dead?

      As this column has shown, the CLR provides significant benefits to developers who are using COM today. Virtually all aspects of the COM programming model have survived (interfaces, classes, attributes, context, and so on). Some may consider COM dead simply because CLR objects don't rely on IUnknown-compliant vptrs/vtbls. I look at the CLR as breathing new life into the programming model that I've spent the last seven years of my life working with, and I know there are other programmers out there who share this sentiment.
Don Box is a cofounder of DevelopMentor, providing education and support to the software industry. Don wrote Essential COM and co-wrote Effective COM and Essential XML (all Addison Wesley). He coauthored the W3C SOAP spec. Reach Don at http://www.develop.com/dbox.

From the December 2000 issue of MSDN Magazine

Page view tracker