//
you're reading...
.NET/COM/ActiveX Interop, COM

The Significance of the COM coclass.

1. Introduction.

1.1 Intermediate COM application developers generally understand the COM development process and are familiar with most of the ubiquitous terms, phrases and concepts, e.g. IUnknown, IDispatch, interfaces, reference counting, interface querying, registration, progID, events, etc.

1.2 The keyword “coclass”, however, remains mysterious and relatively misunderstood. The reason for this is probably due to its perceived lack of relevance.

1.3 This blog explores the significance of the coclass statement, what it is intended to mean, and how it affects the processing of the .NET Type Library Importer (TLBIMP.EXE).

2. Some General Background on the coclass.

2.1 “coclass” is an IDL keyword that is used to describe a COM class. It is COM’s way of defining a class (class in the object-oriented sense) which is meant to be language independent. It specifies the interfaces and dispinterfaces exported from a COM class as well as the source (event) interfaces that the COM class supports.

2.2 As an illustration, let us observe the following coclass statement in IDL :

[
	uuid(AF01ED11-C322-457A-B5A2-A6243FB8E174),
	helpstring("MyObject Class")
]
coclass MyObject
{
	[default] interface IMyObject;
	[default, source] dispinterface _IMyObjectEvents;
};

The above coclass statement declares a COM class named MyObject which implements an interface named IMyObject and which supports (not implements) the event interface _IMyObjectEvents.

Ignoring the event interface bit, this is conceptually equivalent to defining a C++ class like this :

class CSomeObject : public ISomeInterface
{
  ...
  ...
  ...
};

where ISomeInterface is a C++ virtual class.

A coclass, then, is an object-oriented class the instances of which runs in the COM-subsystem.

2.3 A coclass is also indelibly connected with its class ID (CLSID) which identifies it uniquely. Whenever a coclass is to be instantiated, its CLSID is always specified.

3. The coclass and the Concept of the Programming Language Class.

3.1 Whenever it is said that a developer wants to “develop a COM object”, what is actually meant is that he/she wants to create a COM class from which a COM object is instantiated. This COM class is also known as a coclass.

3.2 As the starting point, the developer would launch a language compiler which supports COM development. The onus is on the compiler to supply whatever language facilities (classes, structs, macros and typedefs, etc) necessary for its user to implement and ultimately produce a binary which can be deemed by COM to be a coclass.

3.3 For example :

  • VB has its class constructs which can be seamlessly exported as COM classes.
  • C++ developers use ATL to produce template-driven C++ classes that represent COM classes.
  • C# has its class constructs which uses attributes (e.g. ComVisibleAttribute, GuidAttribute, etc) to implement classes which can be instantiated as COM objects through the help of COM-callable wrappers.

The language-specific code constructs and facilities generally serve to make programming life easier by providing a model and view of object-oriented development (peculiar to the specific language) with which the programmer is familiar.

It is this object-oriented development paradigm that is used for COM development and the coclass that is to be produced usually maps to a specific class construct that is developed in that language.

3.4 The specific language must also generally support the following concepts or their equivalents :

  • Class methods and properties.
  • Interfaces.

These are necessary code constucts in order for the language to fully support COM properties, methods and interfaces.

4. The Type Library and Its Reference by Client Projects.

4.1 Along with providing the language facilities for producing code that can be mapped to a coclass, the language compiler is also oblidged to produce a type library (.TLB file) that contains information for the coclass produced by the high-level language.

4.2 This type library file is specified to be binary in nature.

4.3 The main purpose of producing such a type library file is to allow client application development environments to reference it.

4.4 The end result of such .TLB referencing (by the various development environments) is that the target environment produces the language-specific constructs (classes, structs, macros and typedefs, etc) that will represent the coclass defined in the .TLB.

4.5 This representation is what allows the client application code to instantiate the coclass. We shall discuss this in greater depth starting from the next section.

4.6 The environment used for the client application development can be any as long as it supports COM development. In this sense, the information about the coclass contained in the type library can be considered language-independent.

4.7 This type library referencing is also known as “importing” in VC++ (by the use of the #import statement), In the VB6.0 and VC# environments, the type library is referenced by project settings.

5. COM Class Representations in Various Language Environments.

5.1 As mentioned in section 4, once a type library has been produced from the development project for a COM class, the next thing to do is for the individual client development environment to reference the type library and appropriately interpret it to produce whatever code (in the target development language) necessary for the developer to instantiate the coclass.

5.2 After being referenced, a coclass is usually encapsulated as a class or whatever matches the concept of a class in the target language.

5.3 These classes (that represent coclasses) pave the way for easier coding by providing a model and view (peculiar to the specific language) of a coclass with which the programmer is familar. This is equal and opposite to the situation where the coclass was being developed.

5.4 In terms of coclass instantiation, again each language has its own way of actualizing this :

  • C++ users usually instantiate by using smart pointer classes produced by the compiler through the use of the #import statement.
  • Both VB 6.0 and C# supply the new keyword.

5.5 Instead of developing examples for each specific language development environment, I will confine to the study of what happens when a type library is referenced in the Visual C# environment. This is expounded in the sections that follow.

6. Referencing a Type Library in Visual C#.

6.1 This may come as a surprise to the reader, but a managed language compiler like Visual C# really does not have any intrinsic capability to process a COM Type Library.

6.2 A managed language compiler only understands .NET metadata as a source of type information. The key to exposing COM type information to the .NET world is by taking a COM Type Library and producing the equivalent .NET metadata for it. This process is accomplished by using the Type Library Importer (TLBIMP.exe) tool.

6.3 When we add a reference to a COM type library in the Visual C# environment, the Type Library Importer is invoked under the hood to produce an assembly that contains the COM Type Information in the form of .NET metadata. It is this new assembly (known as an interop assembly) and not the type library, that is being referenced.

6.4 The interop assembly contains the .NET equivalent definitions of COM types that can be referenced from managed language code. Unlike a typical assembly, which contains both metadata and IL (Intermediate Language) code, an interop assembly contains only metadata.

6.5 Among the types contained in a type library, the COM coclass is given special attention. Normally, when a COM class has been instantiated, the client application’s sole means of usage of the object is through its interfaces. This is where QueryInterface() gets involved if the object implements multiple interfaces.

6.6 Due to historical reasons connected with Visual Basic 6.0, the type library importer creates the following classes when it encounters a coclass in a type library :

  • A .NET class with the same name as the coclass but appended with the word “Class”. This is also known as the Runtime-Callable Wrapper class (RCW class).
  • A .NET interface with the same name as the coclass. This is also known as a coclass interface.

In section 7, I will explain in detail the RCW class. In section 8, I will provide an account on the coclass interface.

7. The RCW Class.

7.1 As an example, let us use the coclass which we first encountered in point 2.2 and provide a listing of the definition of the IMyObject interface :

[
	object,
	uuid(56367B67-76D7-4938-B5C0-05D6FA9E2926),
	dual,
	nonextensible,
	helpstring("IMyObject Interface"),
	pointer_default(unique)
]
interface IMyObject : IDispatch
{
	[id(1), helpstring("method Method01")] HRESULT Method01(void);
	[propget, id(2), helpstring("property LongProperty")] HRESULT LongProperty([out, retval] LONG* pVal);
	[propput, id(2), helpstring("property LongProperty")] HRESULT LongProperty([in] LONG newVal);
};

[
	uuid(AF01ED11-C322-457A-B5A2-A6243FB8E174),
	helpstring("MyObject Class")
]
coclass MyObject
{
	[default] interface IMyObject;
	[default, source] dispinterface _IMyObjectEvents;
};

7.2 When referenced inside a C# project, the following “MyObjectClass” .NET class will be created for the MyObject coclass :

The following are the pertinent points about the MyObjectClass :

  • A public default constructor is created for it (by the Type Library Importer).
  • All methods and properties of all interfaces implemented by this coclass will be included as part of this class.
  • For example, as can be seen above, the Method01() method and the LongProperty property (both of the COM interface IMyObject) are defined as members of this class.
  • All source (event) interfaces supported by this coclass will be included as event delegates of this class.

The MyObjectClass RCW class is created by the Type Library Importer so that the developer can conveniently use the new keyword to directly instantiate a COM coclass and be able to access all methods and properties and connect with all supported events of that class, e.g. :

private static void TestMyObjectClass()
{
    MyObjectClass my_object = new MyObjectClass();

    // Connect with the Event01 event.
    my_object.Event01 += Event01EventHandler;

    // Set the value for the object's property.
    my_object.LongProperty = 100;
    // Call the object's method.
    my_object.Method01();
}

7.3 Without the MyObjectClass class, an instance of the MyObject coclass would have to be obtained from one of the techniques described in the next 2 points :

7.4 By using the Activator class methods :

static readonly Guid CLSID_MyObject = new Guid("AF01ED11-C322-457A-B5A2-A6243FB8E174");

private static void TestUseActivator()
{
    IMyObject pIMyObject = (IMyObject)Activator.CreateInstance(Type.GetTypeFromCLSID(CLSID_MyObject));

    pIMyObject.LongProperty = 100;
    pIMyObject.Method01();
}

7.5 By using low-level APIs like CoCreateInstance() :

static readonly Guid IID_IUnknown = new Guid("00000000-0000-0000-C000-000000000046");
static readonly Guid CLSID_MyObject = new Guid("AF01ED11-C322-457A-B5A2-A6243FB8E174");

[Flags]
enum CLSCTX : uint
{
    CLSCTX_INPROC_SERVER = 0x1,
    CLSCTX_INPROC_HANDLER = 0x2,
    CLSCTX_LOCAL_SERVER = 0x4,
    CLSCTX_INPROC_SERVER16 = 0x8,
    CLSCTX_REMOTE_SERVER = 0x10,
    CLSCTX_INPROC_HANDLER16 = 0x20,
    CLSCTX_RESERVED1 = 0x40,
    CLSCTX_RESERVED2 = 0x80,
    CLSCTX_RESERVED3 = 0x100,
    CLSCTX_RESERVED4 = 0x200,
    CLSCTX_NO_CODE_DOWNLOAD = 0x400,
    CLSCTX_RESERVED5 = 0x800,
    CLSCTX_NO_CUSTOM_MARSHAL = 0x1000,
    CLSCTX_ENABLE_CODE_DOWNLOAD = 0x2000,
    CLSCTX_NO_FAILURE_LOG = 0x4000,
    CLSCTX_DISABLE_AAA = 0x8000,
    CLSCTX_ENABLE_AAA = 0x10000,
    CLSCTX_FROM_DEFAULT_CONTEXT = 0x20000,
    CLSCTX_INPROC = CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER,
    CLSCTX_SERVER = CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER,
    CLSCTX_ALL = CLSCTX_SERVER | CLSCTX_INPROC_HANDLER
}

[DllImport("ole32.dll", CallingConvention=CallingConvention.StdCall)]
static extern UInt32 CoCreateInstance
    (
      [In, MarshalAs(UnmanagedType.LPStruct)] Guid rclsid,
      [In, MarshalAs(UnmanagedType.IUnknown)] object pUnkOuter,
      [In] CLSCTX dwClsContext,
      [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid,
      [Out] out IntPtr pInterfaceReceiver
    );

private static void TestUseCoCreateInstance()
{
    IntPtr pIUnknownReceiver = IntPtr.Zero;
    UInt32 hrRet = CoCreateInstance
    (
      CLSID_MyObject,
      null,
      CLSCTX.CLSCTX_INPROC_SERVER,
      IID_IUnknown,
      out pIUnknownReceiver
    );

    IMyObject pIMyObject = (IMyObject)Marshal.GetObjectForIUnknown(pIUnknownReceiver);
    Marshal.Release(pIUnknownReceiver);
    pIUnknownReceiver = IntPtr.Zero;

    pIMyObject.LongProperty = 100;
    pIMyObject.Method01();
}

7.6 Furthermore, the techniques presented above only allows the methods and properties of one single interface from the object to be accessed.

Additionally, a special value of a RCW class is that it is only through the use of the RCW class that delegate-based event handlers can be setup. As an example, suppose the _IMyObjectEvents interface, which is a source event interface supported by the MyObject coclass, is defined as follows :

[
	uuid(F68DEE08-0E75-4ED3-830E-3272D71D3BC9),
	helpstring("_IMyObjectEvents Interface")
]
dispinterface _IMyObjectEvents
{
	properties:
	methods:
		[id(1), helpstring("method Event01")] HRESULT Event01(LONG lValue);
};

Then it is only through a RCW class instance that it is ever possible to setup a delegate-based event handler :

public delegate void _IMyObjectEvents_Event01EventHandler(int lValue);

private static void TestMyObjectClass()
{
    MyObjectClass my_object = new MyObjectClass();

    // Connect with the Event01 event.
    my_object.Event01 += Event01EventHandler;

    my_object.LongProperty = 100;
    my_object.Method01();
}

private static void Event01EventHandler(int lValue)
{
    Console.WriteLine("Event01 : {0:D}.", lValue);
}

7.7 A word of Caution. However, take caution that the Type Library Importer will blindly define the RCW class according to how the corresponding coclass is defined in the type library. If, for instance, there is another interface defined in the type library and the MyObject coclass is defined to implement it :

[
	object,
	uuid(FCEF46BD-33E7-4B21-B160-3C086A5D6C9D),
	dual,
	nonextensible,
	helpstring("IInterface02 Interface"),
	pointer_default(unique)
]
interface IInterface02 : IDispatch
{
	[id(1), helpstring("method Method01")] HRESULT Method01(void);
};

[
	uuid(AF01ED11-C322-457A-B5A2-A6243FB8E174),
	helpstring("MyObject Class")
]
coclass MyObject
{
	[default] interface IMyObject;
                   interface IInterface02;
	[default, source] dispinterface _IMyObjectEvents;
};

Then, as indicated in point 7.2 (that all methods and properties of all implemented interfaces will be included as members of the RCW class), the MyObjectClass RCW class will include the methods and properties of the IInterface02 interface.

The MyObjectClass RCW class entry as listed in the Object Browser will be displayed as follows :

Notice that there is a new method included for the MyObjectClass RCW class :

void IInterface02_Method01();

This method corresponds to Method01() of IInterface02. It is renamed with a IInterface02 prefix due to the fact that Method01() coincides with a method of the same name of the IMyObject interface.

However, if the MyObject coclass in actual fact does not implement IInterface02, then a System.InvalidCastException will be thrown when IInterface02_Method01() is called :

// System.InvalidCastException will be thrown
// when the following call is made because
// the MyObject coclass in actual fact does
// not implement the IInterface02 interface.
my_object.IInterface02_Method01();

8. The coclass Interface.

8.1 This entity is a holdover from the days of Visual Basic 6.0.

8.2 In Visual Basic 6.0, for every coclass contained in a referenced type library, a special class interface class is created which contains all methods and properties of the default interface of the coclass and which supports all methods and properties of the default source interface of the coclass.

8.3 The Type Library Importer performs the same kind of coclass interface creation when it encounters a coclass contained inside a type library.

8.4 As an example, let us refer to the MyObject coclass as listed in point 7.7 where the MyObject coclass implements both IMyObject and the IInterface02 interfaces. The Type Library Importer will create a coclass interface with the same name as the coclass itself (MyObject) :

[Guid("56367B67-76D7-4938-B5C0-05D6FA9E2926")]
[CoClass(typeof(MyObjectClass))]
public interface MyObject : IMyObject, _IMyObjectEvents_Event
{
}

The following are a summary of the important points of the above interface declaration :

  • The GuidAttribute bears the same GUID value as that of the IMyObject COM interface.
  • The CoClassAttribute argument is MyObjectClass. Hence the current managed interface acts (as the managed coclass interface) for the COM colass MyObject.
  • Notice that the MyObject coclass interface is derived from IMyObject and _IMyObjectEvents_Event which indicates that the MyObject coclass interface inherits all methods and properties of the COM IMyObject interface which is the default interface.
  • It further inherits the event delegate Event01 which is from _IMyObjectEvents_Event, the default source interface.
  • Notice that even though the MyObject coclass as defined in point 7.7 lists IInterface02 as an implemented interface, the MyObject coclass interface does not inherit the methods and properties of this interface since IInterface02 is not the default interface.
  • Neither will it inherit any non-default source interfaces.

8.6 The argument for CoClassAttribute is particularly important because it allows code like the following to compile and then run successfully :

private static void TestCoclassInterface()
{
    MyObject my_object = new MyObject();

    my_object.Event01 += Event01EventHandler;

    my_object.LongProperty = 100;
    my_object.Method01();
}

8.7 Notice the rather odd looking code :

MyObject my_object = new MyObject();

This is strange because here MyObject is actually an interface and is not a class. However, the code is treated by the compiler as :

MyObject my_object = new MyObjectClass();

The reason for this is directly because of the fact that the CoClassAttribute applied to the MyObject interface cites MyObjectClass.

9. In Conclusion.

9.1 And there we have it. As complete a treatise as possible on the COM coclass.

9.2 The amazing thing about a COM coclass is that it can be written in one specific language and yet be instantiated in a client application written in possibly another language. This is achieved due to the binary standard of the v-table which is exploited by COM.

9.3 And this is the true intent and spirit of the coclass in the COM world : to perform the role of a language-independent object-oriented class that runs in the COM sub-system.

9.4 This principle has profoundly influenced the way programmers build systems and is the foundation of newer environments like the .NET framework.

Advertisements

About Lim Bio Liong

I've been in software development for nearly 20 years specializing in C , COM and C#. It's truly an exicting time we live in, with so much resources at our disposal to gain and share knowledge. I hope my blog will serve a small part in this global knowledge sharing network. For many years now I've been deeply involved with C development work. However since circa 2010, my current work has required me to use more and more on C# with a particular focus on COM interop. I've also written several articles for CodeProject. However, in recent years I've concentrated my time more on helping others in the MSDN forums. Please feel free to leave a comment whenever you have any constructive criticism over any of my blog posts.

Discussion

4 thoughts on “The Significance of the COM coclass.

  1. Thank you for this grate article. It is very clear and helpful. It clarifies my many questions about coclass, type library, and their relationships with .NET languages. And I like your style of writing (1, 1.1, 1.2, … 2, 2.1, 2.2, …) – concise, clear, and to the point. I enjoy that!

    Posted by Chengwei Lin | April 23, 2012, 8:56 am
  2. THANK YOU

    Posted by AKHIL | May 18, 2012, 1:55 pm
  3. Hi Lim Bio,

    You have a talent to make difficult things understood easily.
    There are not many people having such talent.

    HaeRim Lee

    Posted by HaeRim Lee | July 11, 2014, 9:28 pm

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: