You are on page 1of 50

.

NET and COM: Working Together

by Brian Noyes

March 2001
© Fawcette Technical Publications Inc.
Get the Code
Download the code for this book at
www.devx.com/free/mgznarch/vcdj/code/mightywords/DotNetAndCOMCode.zip

The code is broken into three sections, corresponding to the three sections of the
book:
• Part 1 contains the sample code for implementing basic COM components in
.NET.
• Part 2 contains the sample code for implementing ActiveX controls in .NET.
• Part 3 contains the sample code for consuming COM components and
ActiveX controls in a .NET application.

The Mp3HeaderReader and MP3CatalogCtl projects are duplicated in each of the


parts because of peculiarities in managing references in Beta 1 of VS.NET. The
MP3CatalogCtl.cs file is slightly modified between the Part 2 and Part 3 versions.
Otherwise, the files are all the same. Each part has its own Readme.txt file that
discusses how to build and run the samples.

Acknowledgements
Very few technical writing projects are the result of a single person’s knowledge
and efforts. This book is no exception. I would like to thank Elden Nelson for his
guidance and help getting started with this project. I also wanted to thank Eric
Kinateder both for his help in discovering the magic recipe for simple COM
components discussed in section one, and for his efforts as technical editor for this
book. Dennis Angeline, Mark Boulter and Mike Harsh from Microsoft were
helpful in figuring out when I was doing things wrong, and when I was just
running into the unique personality of Beta 1. James Sievert also helped discover
some of the ways that sourcing COM events worked in Beta 1. Finally, and most
important in all things, my wife Robin is a constant counselor, coach, and best
friend, and without her patience and tolerance for the long hours, I never could
have completed this and countless other projects.

Page 1 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


.NET and COM:
Working Together
What you need
! .NET SDK Beta 1
! Familiarity with ActiveX Controls
! Familiarity with the C# Language
! Visual Studio.NET Beta 1

Introduction
The .NET Framework is the next generation development platform from
Microsoft, and will enable rapid development of more robust and scalable code.
The terminology can get a little confusion since .NET is still in Beta at the time of
this writing, and the buzzwords change faster than the publications can keep up
with them. But the .NET Framework consists of a runtime engine, called the
Common Language Runtime (CLR), and a set of reusable classes and components
that make up the .NET Framework library. Applications and components
developed for .NET will run as managed code, meaning that they will run on top
of the CLR and will have automatic memory management, versioning, security,
and other features handled by the runtime. Applications developed to run outside
the CLR are called unmanaged code, which translates to everything else, such as
native code compiled directly to machine executable formats and code designed
to run in other runtime engines, such as the Java Virtual Machine. Visual
Studio.NET is the next generation of the Visual Studio development environment
(in other words, version 7), and will enable rapid application development for the
.NET environment or native code through numerous languages.
An important goal in the .NET Framework is interoperability between .NET
code and existing code. This includes any code that runs outside of the CLR and
the managed environment, including COM components and applications. Since
COM components and COM-aware unmanaged applications will be around for a
long time to come, it will be important to know how to write .NET components
that can be exposed to unmanaged clients as COM components, as well as how to
consume COM components in a .NET application.
This book will explore the COM interoperability features built in to the .NET
Framework and show how to build COM components in C# as well as to consume
COM components from within C# applications. The .NET Framework and Visual
Studio.NET allow you to develop .NET applications in a variety of languages,
including managed and unmanaged C++, Visual Basic.NET, JScript.NET, and the
all new language C# (pronounced C-Sharp). Visual Basic.NET and JScript.NET
will be compiled to run in the managed environment, as opposed to Visual Basic
6 applications that were compiled to run using the Visual Basic runtime.

Page 2 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


I have chosen to use C# for this book for a number of reasons. If you are
familiar with C++ or Java, even if you have never programmed in C# before, you
will find C# easy to understand with little introduction. The syntax follows that of
C++, but the language has been designed as an object oriented language from the
ground up, much like Java. But a lot of lessons learned have been incorporated
from the shortcomings of both C++ and Java. I believe that C++ and Java
programmers alike will find C# a very powerful and easy to use language, and I
think it will be the language of choice for developing .NET applications. It is also
a very clean and easy to read language, so it is good for demonstration of
capabilities in a book such as this.
So you do not need to know C# to read this book, but familiarity with C++ or
Java will definitely help. This will not be a tutorial on C#, so I will not be diving
into the language features associated with the code unless required to explain
what is going on. You do need to have an understanding of the COM and COM+
architectures, and should know how components are built and used from either
C++, Visual Basic, or Java at a basic level, so that you can draw the parallels to
how you do it in the .NET framework. I also assume you know how to use COM
components from a Visual C++ 6 application using the #import capabilities, or by
using Project > References to add a COM type library and use it in VB6.
This book is broken into three main sections. Building non-visual COM
components is distinctly different and simpler than building visual (ActiveX)
controls, both in managed and unmanaged code. So the first section will explain
how to expose a C# component as a COM component to unmanaged clients. I will
explain the features and parts of the .NET Framework that enables you to do this
very easily with an easy to follow cookbook approach. I will first demonstrate the
concepts with a minimal C# class that I expose as a COM component, and show
how to consume it from a simple C++ client. Then I will briefly describe a more
complex component that is more representative of a real world component, and
that is included in the downloadable code for this book. This component reads
MP3 track information from MP3 files on your system, and catalogs the
information in an XML stream that can be saved to disk or used by other
applications.
In the second part, I will dive into how to expose a .NET visual component,
derived from the WinForm .NET class, to unmanaged applications that can host
ActiveX controls. I will build a simple control that uses the MP3 header reader
component from part one and displays the cataloged information in tabular form. I
will show how to host this control in an unmanaged C++ application. Then I will
cover how to expose .NET events to COM clients and incorporate these events
into the sample application.
In the last section, I will reverse the roles and cover how to consume COM
components from a .NET application. I will continue to build on the components
developed in the previous sections, and integrate them with the Windows Media
player, which can be integrated into an application using COM interfaces. I will
cover the various aspects and options available to control how COM components
are instantiated and run within a managed application.

Page 3 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


Exposing .NET
Components to COM
Clients
Build a COM component with C#
Component based development is widely recognized as one of the best
approaches available to develop reusable software. COM has provided the means
to build components in a variety of languages prior to the advent of the .NET
Framework. COM provides cross language compatibility at the binary level by
specifying what the layout of interfaces and their methods need to be within the
code module. Specifying things at the binary level like this allows code from
other languages to find the interface methods and use them based on this binary
contract that modules must adhere to. COM also uses type libraries and Interface
Definition Language (IDL) specifications to allow code in other languages to
discover what interfaces and methods a component supports, and what their
method parameters are. With COM, you also have to make sure that components
are properly registered in the Windows Registry so that the Windows COM
runtime can locate and instantiate your components properly. With COM+, there
are a number of additional settings you can make in the COM+ Catalog to
indicate various COM+ services that the component supports or requires. And
then on top of all that, each language has its own peculiarities about how you
actually go about writing the code for a COM component that is usually
somewhat different than the code required for a normal object oriented or
procedural program in that language. Finally, when it comes to versioning and
updating components, there are a number of design guidelines for COM to try to
keep you out of trouble, but they are almost entirely dependent on how well the
component developers follow those guidelines.
The .NET Framework makes deploying and running components far simpler,
especially if the components and clients will all live in the managed environment.
First, the .NET Framework is designed to hide most of the hassles of registration,
design time and run time type discovery, versioning, and type specifications. It
does all this by examining the code at compile time and creating assemblies and
their embedded metadata to describe the component’s capabilities and identity.
Part of the .NET Framework is the definition of the Common Language
Specification (CLS). Compliance with this specification in your code is supposed
to ensure that your code can be accessed seamless across other languages that
comply with the CLS. Although non-compliant data types and calling conventions
can be used in languages and compiled and run in the managed environment, you
will probably not be able to access and use classes in your code in different
languages without some type compatibility problems. This is somewhat similar to
COM requirements for binary compatibility, but in .NET the commonality is
Page 4 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om
specified at the Intermediate Language (IL) level, which is what is passed to the
CLR for execution.
The C# language was designed as a component oriented language from the
ground up. The distinctions between component oriented development and object
oriented development are often fuzzy because most people develop components
using object oriented languages and concepts. And there are also a lot of overlaps
between object oriented and component oriented concepts. But one of the
important concepts in component-based development is that components package
units of functionality in a way that enables reuse from multiple languages and
they expose that functionality through interfaces, properties and methods. C#
supports these constructs as the primary constructs of classes. This makes class
and component virtually synonymous in C#. However, you should be aware that
another widely accepted definition of component considers a component to be a
collection of types, such as a COM server DLL or a .NET assembly. As long as
the types bundled into a single library all have a common purpose, this definition
is equally viable. So if you are going to talk about components, you need to be
explicit about what you consider to be a component – a type or a library. In these
articles, I will be using the type definition of component.
Build Your First .NET COM component
If you want to understand the basic steps required in creating a COM component
from a C# class, the best way is to first understand how to do it outside of the
Visual Studio environment using command line tools. Then, from there you can
figure out how to let Visual Studio save you some manual effort in the future. Of
course, I am not encouraging you to write your code outside of VS.NET. The
automatic statement completion, syntax checking, dynamic help, and other
features in VS.NET make it far more instructive and productive to write your
code in the IDE. You just might want to do the compilation steps from the
command line a few times to get used to what all the required steps are. It can
help you understand what might be missing if you start developing more complex
projects later and things stop working the way you expect.
The information on how to build COM components from .NET is all there in the
.NET SDK documentation, but it can be a little difficult to find the steps required
to get you up and running at the most basic level. This easy to use cookbook
approach to creating a COM component from C# will allow you to create a simple
component quickly and easily. I will go into more detail on each of these steps
later, but let’s get you up and running first.
The steps to create a COM component from a simple C# class are as follows:
1. Define a C# class with public visibility and with a public default
constructor that you want to expose as a COM coclass.
2. Add public methods that you want to be exposed as COM interface
methods on the coclass default interface.
3. Create a shared key file using the Shared Name Utility (sn.exe), or locate
an existing shared key file that you wish to use. You can create a shared
key file with this syntax at the command prompt:
sn -k demoKey.snk

Page 5 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


4. Compile your C# assembly using the command line compiler with this
syntax:
csc /a.keyfile:demoKey.snk /target:library simplecomponentlib.cs
5. Register the assembly using the regasm.exe tool so that the appropriate
COM entries are made in the registry (CLSID, ProgID, etc.):
regasm /tlb:simplecomponentlib.tlb simplecomponentlib.dll
6. Either place the assembly in the path of the client, or install the assembly
into the global assembly cache using the Global Assembly Cache Utility
(gacutil.exe) from the command line. The syntax for using gacutil.exe
for this is as follows:
gacutil –i simplecomponentlib.dll
7. Import or reference the type library for your component in your client
application (such as a C++ or VB application), and away you go.
So there is the “Joy of C# COM Cooking” recipe. Now lets pick it apart a little
at a time to understand what’s going on here, and to gain some insight into the
way the .NET runtime and framework provide COM compatibility.
Defining the COM-Creatable Class
Defining a class that can be exposed as a COM component is simple, because the
.NET Framework and CLR take care of most of those peripheral details that you
used to have to worry about in COM. All you need is a public class with a public
default constructor, and the class can be exposed as a COM coclass. Any non-
static public member methods defined in the class will also be exposed as COM
methods on the default class interface. Public C# properties and fields will also be
exposed as COM properties on their respective interface using get_ and put_
methods automatically.
I will define our simple class as follows, and save it in a file named
simplecomponentlib.cs:
namespace simplecomponentlib
{
using System;

public class simplecomponent


{
public simplecomponent() { }

public string GetGreeting ()


{
return "Hello from a .NET component!";
}
}
}

When the class is invoked from COM, the CLR will create a COM Callable
Wrapper (CCW) that acts as a COM proxy object for the COM client. It will also
create a default interface for your class that consists of the name of the class
prefixed with an underscore. If you derive your class from other interfaces, it will
expose those as well, with their names unadorned. If you have public constructors
that take parameters, they will not be exposed in the default interface. Also, if you

Page 6 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


have overloaded public methods, their names will be adorned with a trailing
sequence number (e.g. HRESULT OriginalMethod(…), HRESULT
OriginalMethod_2(…)) because COM does not permit method overloading in
interfaces.
The proxy object that your application will use lives outside the managed
environment and maintains a reference count, just like other COM objects. The
CCW lives on the unmanaged heap and maintains references on the .NET object
that is in the managed environment, and can therefore move around in memory.
The CLR will work with the CCW to keep its references to the .NET object up to
date. The CCW handles marshalling calls between the managed component and
the unmanaged client (see Figure 1). When the reference count on the proxy drops
to zero, the CCW is released and the .NET object becomes eligible for garbage
collection, and will be cleaned up the next time the garbage collector runs.

Figure 1. The COM Callable Wrapper (CCW). The CLR creates a CCW to act as a proxy object to
bridge the managed and unmanaged environments between your .NET component and the COM
client. The CCW handles reference counting and resulting component lifetime and garbage
collection, as well as marshalling data between the managed and unmanaged environments.

Before I get too deep into what the runtime does by default, you should realize
that almost everything that the runtime does for you automatically can be
overridden by specifying explicitly what kinds of services you want it to make
available. This is done through the use of a rich set of COM related Attributes that
are defined within the .NET Framework. I will mention many of these as I go, so
that you know where to look when you want to start developing your own .NET
COM components and have complete control over what the runtime does for your
on your behalf. If you want to dig deeper, look into the documentation for the
Microsoft.ComServices and System.Runtime.InteropServices namespaces.
Note: Attributes in .NET can be specified with their full name, which usually is
suffixed with the word Attribute (i.e. GuidAttribute), but they can be specified in
your code using the abbreviated form which drops the Attribute suffix (i.e. Guid).
For clarity, I will use the longer form in the discussion so you know when I am

Page 7 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


talking about an attribute vice another type, method or property, but will use the
abbreviated version in the code.
By default, the ProgID of the class will be composed of the namespace and the
name of the class. If a GUID is not specified for the class, one will be created
based on the name of the class. The GUID should not change if the class
definition does not change, but it is probably better to be in control and
deterministic about things and assign these things explicitly. You do this using
ProgIdAttribute applied to the class, and GuidAttribute applied to the assembly,
class or interfaces. A GUID specified for the class sets the coclass GUID. A
GUID specified for an interface sets the GUID for that interface. There is no way
to control the GUID that is generated for the default class interface, which will be
discussed a bit more shortly.
So in the case of our simplecomponent, the default ProgID will be
simplecomponentlib.simplecomponent, the default class interface will be
_simplecomponent, and the GetGreeting() method will be exposed as a method on
the _simplecomponent interface to COM clients. You will also find that a number
of other methods are exposed on the default class interface for every object you
export. This is because the class interface exposes all public methods on the class
- and all classes and interfaces from which it derives. Since all C# classes
implicitly derive from Object, at a minimum you get the Equals(),
GetHashCode(), GetType() methods, as well as the ToString property defined in
all of your default class interfaces. The generated IDL for this interface looks like
this:

[
odl,
uuid(38ACA84B-1897-3218-87EC-953719452792),
hidden,
dual,
nonextensible,
oleautomation,
custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9},
"simplecomponentlib.simplecomponent")

]
interface _simplecomponent : IDispatch {
[id(00000000), propget]
HRESULT ToString([out, retval] BSTR* pRetVal);
[id(0x60020001)]
HRESULT Equals(
[in] VARIANT obj,
[out, retval] VARIANT_BOOL* pRetVal);
[id(0x60020002)]
HRESULT GetHashCode([out, retval] long* pRetVal);
[id(0x60020003)]
HRESULT GetType([out, retval] _Type** pRetVal);
[id(0x60020004)]
HRESULT GetGreeting([out, retval] BSTR* pRetVal);
};

Page 8 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


The class interface is created as a dual, IDispatch derived interface, and the
DISPIDs are automatically generated, but once again, this can be overridden with
DispIDAttribute. Also note that the default method for the dispinterface will be
the get_ToString() method that returns the ToString property. In the object base
class version of the method, this simply returns the type of the class name as a
string, unless overridden in your class or its base classes.

The Case for Interface


Even though the .NET runtime and tools will provide you with a default interface
for your class, there are a number of good reasons that you should not blindly use
that interface to expose your functionality, but should instead define interfaces for
any functionality that you want to expose to COM. The first reason is just good
object oriented and component design principles. You should always try to factor
out related functionality into interfaces that your component’s clients can count
on to be an immutable contract. This makes the component's purpose more clear
and makes it easier for consumer to understand. Another reason is that by
factoring your public methods into interfaces, you can avoid requiring your clients
to deal with the clutter of all the other stuff that will appear in the default
interface. Finally, since a class is likely to evolve over time, it is highly likely that
its default interface will change as methods or properties are added to the class.
But one of the key rules of COM is that an interface must be immutable – that its
signature never be changed after it is published. So to comply with this rule, you
could never extend your class without renaming it, which brings along a whole
host of other versioning and reuse problems.
If you specify your component’s behavior through well defined interfaces, and
would like one of those interfaces to be the default interface instead, you can
apply HasDefaultInterfaceAttribute to the class. When you do this, the first
implemented interface in the class will be marked as the default interface when
exposed to COM, and the default class interface will not be generated. You can
also fool the system by marking the class with a ComVisibleAttribute(false), but
HasDefaultInterfaceAttribute is a cleaner approach.
So rather than just using the simplecomponent class as shown earlier, a cleaner
approach would be this code:
Using System.Runtime.InteropServices;

public interface IDoSimpleThings


{
string GetGreeting();
}

[HasDefaultInterface()]
public class simplecomponent : IDoSimpleThings
{
public simplecomponent() { }

public string GetGreeting()


{
return “Hello from a .NET component!”;

Page 9 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


}
}

Compiling and resgistering this version of simplecomponent results in this IDL


code:

// Forward declare all types defined in this typelib


interface IDoSimpleThings;

[
odl,
uuid(95041548-68B2-3F4D-9C56-6EEC92EB5C20),
dual,
oleautomation,
custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9},
"simplecomponentlib.IDoSimpleThings")

]
interface IDoSimpleThings : IDispatch {
[id(0x60020000)]
HRESULT GetGreeting([out, retval] BSTR* pRetVal);
};

[
uuid(AC5DA19B-79D0-30B8-ADF9-1EC0C1DFFE64),
custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9},
"simplecomponentlib.simplecomponent")
]
coclass simplecomponent {
[default] interface IDoSimpleThings;
};

You can see here that the the IDoSimpleThings interface is set as the default
interface with the single GetGreeting() method, and there is no _simplecomponent
interface cluttered with the object base class methods.

Shared Name Key Files


Another important goal in the .NET world is to improve support for identity and
versioning. This is done by embedding public keys in the metadata for an
assembly, which will identify in a secure fashion the creator of the component.
You generate shared name keys using the Shared Name Utility (sn.exe). This
utility will generate a file that contains the public and private key pair that can be
used to authenticate the identity of the component. The general concept is that a
given company will likely have a protected set of keys that it uses to identify its
products. You can either make these files accessible to your developers and they
can compile them into the assembly at build time, or you can use attributes to
reserve space in the assembly metadata for the keys, and those keys can be added
in later after the assembly has been built and tested in a non-authenticated
environment. If you are just building assemblies that will be run in .NET without
sharing assemblies, you don't need to compile in shared name keys at all.
Page 10 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om
However, when you are developing COM components in .NET, the CLR uses
these keys as part of the identity of the component, so you need to make sure you
include shared name keys in your assemblies targeted for COM clients, unless
you will be deploying them to the same directory as the host application. You do
this using the /a.keyfile: compiler switch to identify the shared name key file, or
you can accomplish the same thing using AssemblyKeyFileAttribute on the
assembly. You can use AssemblyDelaySignAttribute if you just want to reserve
room for the keys, and add them in later. For example, to set the key file within a
C# module, you an just declare this attribute at Assembly scope:

[assembl y: Assembl yKeyFile("demoKey .snk")]

Compiling the Assembly


You can compile the assembly using the command line C# compiler (csc.exe) that
comes with the .NET SDK, or you can use the Visual Studio.NET environment to
avoid the intimidating experience of living at the command prompt. I will get
more into using the Visual Studio environment for later projects, so for now lets
stick to the command line compiler.
To use the command line compiler, use this line:

csc /a.keyfi le:demoKey.s nk /target: library simplec omponentlib. cs

This line tells the compiler to include the shared name key file specified, make
the output executable format a library (DLL) instead of an EXE, and specifies the
source file(s) that need to be compiled into the assembly. If additional assemblies
were referenced in the code (like if there was a “using System.XML” statement),
then those additional modules would be specified with a /r: compiler switch (i.e.
/r:System.XML.dll). Since C# files use namespaces to keep things separate and
are agnostic when it comes to compilation order, the order that you specify
additional source code files is unimportant. The only important one is the first one
you specify, since that is the one that will be used to name the resulting assembly
by default (it will take on the same name), unless the /out parameter is used to set
it explicitly.
Registering the Assembly With COM
As you should know, the COM runtime figures out where to find COM libraries
and how to instantiate them from settings in the Windows registry. Additionally,
if the component is a COM+ component, COM+ services look in the COM+
catalog to see what other configuration items are set for the component, such as
whether it supports or requires transactions, can be pooled, or requires
synchronization. When you develop COM components in C++ or other
languages, the registry information gets set when a tool such as regsvr32.exe calls
a couple of standard functions in the library called DllRegisterServer() and
DllUnregisterServer(). These functions are responsible for ensuring all the proper
entries for the components in the library are either added or removed,
respectively, in the registry. The COM+ catalog entries are set either declaratively

Page 11 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


through the Component Services Explorer or programmatically by accessing the
object model of the COM+ services.
However, a .NET assembly is laid out differently inside the library file or files
that compose it, and they do not have the standard registry functions built in. So
you cannot just point a COM tool at the assembly and expect it to register the
library with COM. Instead, you need to use the Register Assembly tool
(regasm.exe) from the .NET SDK, which looks at the assembly metadata and
figures out how to make the appropriate COM entries in the registry and the
catalog based on the metadata.
You can also use regasm.exe to create a type library module (.tlb) that contains
the COM type library information for the assembly in the same step as registering
the component and type library information in the registry. To do this, you just
use the /tlb: switch when running the tool. If you want to do this separately, there
is another tool called the Type Library Export Utility (tlbexp.exe) that will just do
the type library creation for you. Since .NET Assemblies do not contain any of the
COM specific type information that a COM DLL could, having a separate type
library file (.tlb) becomes event more important for compatibility with COM
development tools and clients.
So to register the public classes and interfaces in an assembly as COM coclasses
and interfaces, and to create a type library module file that can be used with other
development tools all in one step, just use this syntax at the command line,
specifying the target type library filename and the source assembly file name.

regasm. exe /tlb:sim plecomponent lib.tlb simp lecomponentl ib.dll

This will register all the COM creatable types in the assembly as coclasses, the
public interfaces as COM interfaces, and will also register the type library. It will
make registry entries such as the ProgID and CLSID for the coclasses, and will
register all the appropriate subkeys. It adds a few additional subkeys beyond what
a vanilla COM component has that the .NET runtime will use to help identify and
instantiate the component. There are a number of nuances in how the runtime and
the regasm tool will handle name clashes and generation of the various GUIDs for
the elements in the type library if they are not stated explicitly. See the .NET
Framework SDK documentation on “Exposing .NET objects to COM” for more
details. When the coclasses are registered, they all will be associated with the
mscoree.dll runtime assembly, as opposed to the actual assembly that contains the
components, as would normally be done in COM. It will be up to the runtime to
locate the actual implementation assembly for the components at runtime, as
discussed next.

Page 12 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


Figure 2. Registry Settings for simplecomponent. COM registered .NET assemblies point the
InprocServer32 key to the MSCorEE.dll runtime library, and add an Assembly value that help the
runtime resolve the identity of the real assembly that implements the component.

When the .NET components get registered, they are marked by default with the
Both threading model. This means that they will be created in either a single
threaded apartment or a multi-threaded apartment, depending on the threading
model of the client that is creating the component. You can set whether the
component requires synchronization or transactions or other COM+ services
using additional attributes on the class.
Normal COM component DLL’s contain DllRegisterServer() and
DllUnregisterServer() functions which handle the registration of the component
for you. In these functions, you can add code to do other things besides just
register and unregister the COM components. Since these functions do not exist
by default in a .NET assembly, you may need to provide similar capability in your
.NET COM component. If you have additional initialization that you need your
component to do, you can specify a function to be called at registration and
unregistration time using ComRegisterFunctionAttribute and
ComUnregisterFunctionAttribute. In fact, I will use these attributes later in
Section 2 to make a .NET component available as an ActiveX control.

Making the Components Accessible


So far you have compiled .NET classes into an assembly, and registered those
classes with COM as coclasses and interfaces. Although all this makes it so COM
knows how to locate the components, when COM activates the .NET runtime to
run the components, the CLR needs to be able to locate the assembly itself. The
CLR will first look on the path for the executable that is calling it. So if you
deploy the assembly to the same folder as the executable, then the CLR will find
it no problem and away you go. If you want to locate the assembly elsewhere, the
CLR will also look in something called the global assembly cache to determine if
the assembly it is looking for exists there.
To register an assembly in the global assembly cache, you can use yet another
.NET SDK command line tool to perform this step. The Global Assembly Cache
Utility (gacutil.exe) will install an assembly in the global assembly cache, and can

Page 13 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


be used to uninstall it as well. To use this tool to add the assembly to the cache,
you can simply use this syntax:

gacutil –i simp lecomponent. dll

The –i switch installs an assembly into the cache and the –u switch uninstalls it.
You can specify version numbers if you want to be explicit about which
version(s) get uninstalled. If you do not specify a version with the –u switch, all
versions will be uninstalled. You can also simply delete the corresponding file
from the %Windows%\assemblies folder, but using the SDK tools is probably a
safer solution, since there may be other information that the gacutil cleans up
elsewhere.
Once you have either placed the assembly in the path of the client application or
registered it in the global assembly cache, you should be ready to run.
The download code includes a simple MFC dialog client that uses
simplecomponent through COM. It uses #import to pull in the type library
information from the mscorlib.tlb file, which contains definitions of the .NET
types, and the simplecomponentlib.tlb file generated by regasm.exe. It then uses
smart pointers to instantiate the component and call its GetGreeting() method in
the same way you would consume any simple COM component. You could
construct a similar client in Visual Basic 6 by creating a simple EXE application,
adding the simplecomponentlib type library in the Project > References, and then
creating an instance of the simplecomponent class in your code.

Cranking Things Up a Notch – The MP3HeaderReader Component


The simplecomponent class shown before was kept intentionally simple so that
the simplicity of the conversion from .NET class to COM coclass was not clouded
in any complexity. For a slightly more functional example, the download code
that accompanies this book includes a component called MP3HeaderReader that
searches a specified path, reads in all the headers of MP3 files found on the path
(recursing to sub-directories if desired), and returns the results as an XML
document string.
The class definition for the MP3HeaderReader class is as follows, with all the
logic code and XML documentation stripped out. See the downloadable code for
the full details, including the use of C# XML documentation comments for
interface, class, and methods. If you open the MP3HeaderReaderLibrary.sln
solution file in the MP3HeaderReaderLibrary folder, and open the Solution
Explorer window in Visual Studio.NET, you will see that this solution also
contains the MP3LibClient C# WinForms project that will be described in a bit.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
[assembly: AssemblyKeyFile("testKey.snk")]

namespace MP3HeaderReaderLibrary
{
using System;

Page 14 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


using System.IO;
using System.Xml;

[Guid("3F126E79-EF2A-4bdf-A89B-2ED75131EC3D")]
public interface IReadMP3Headers
{
string ReadHeaders(string path, bool recurse);
string ReadHeader (string filePath);
}

[HasDefaultInterface()]
[Guid("46EF6D6D-2314-48a2-A0B2-DE1D92E818DB")]
[ProgId("MP3HeaderLib.MP3HeaderReader")]
public class MP3HeaderReader : IReadMP3Headers
{
public MP3HeaderReader() {}

public string ReadHeaders (string sourcePathName, bool


recurse)
{
// Calls ReadHeader for each file found and
// packs the results in an XML document string
}
public string ReadHeader (string filePath)
{
// Creates an IP3V1Header instance for the file
// and reads the properties into an XML doc
}
}
}

This code has a number of details worth exploring that go beyond the
simplecomponent example. First you will notice the use of
AssemblyKeyFileAttribute to add the public key information into the metadata for
the assembly. Using this attribute requires information from the CompilerServices
and InteropServices namespaces, so those are first made available with the using
statements at the top. This takes the place of the /a.keyfile parameter passed to the
command line compiler, so we can now compile the component within the Visual
Studio.NET IDE.
Next, a namespace is declared that contains the types that are defined for this
assembly. Since the component will be reading information from the file system
and using XML document classes, I bring in the System.IO and System.XML
namespaces for those capabilities.
To follow good component design, I expose the functionality of this component
through a well-defined and simple interface, IReadMP3Headers, instead of simply
accepting the default class interface that the runtime would generate
automatically. In the download code, you can see that I have used the C# XML
documentation capabilities to document the methods, parameters, and return
values of this interface. When you compile the project in Visual Studio.NET, the
compiler will automatically use this information to generate an XML document
with this information in it in your output directory. You can also see that I used
GuidAttribute to explicitly assign a GUID that I got from the GUIDGen tool from
Visual Studio. This way I know for sure that the GUID won’t change if something

Page 15 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


minor changes in the interface definition. If you do not do this, the .NET SDK
tools and runtime will generate a GUID for the interface based on a hash of its
name and method parameters.
I then define the MP3HeaderReader class and implement the IReadMP3Headers
interface in this class. I use several attributes on this class definition to control the
generation of COM information explicitly. HasDefaultInterfaceAttribute tells the
runtime and SDK tools to use the first interface that the class implements as the
default interface, and will inhibit the creation of the default class interface.
GuidAttribute explicitly sets the coclass GUID for the component, and
ProgIdAttribute sets the ProgID explicitly. As you can see, the class is declared as
public and it has a public default constructor, so the class can therefore be
exposed as a COM coclass.
If you refer to the download code, you will see that the ReadHeaders() method
uses the System.IO classes to find all the MP3 files in a directory, and then calls
ReadHeader() for each file. If the flag is set in ReadHeaders() to recursively
search sub-directories, then ReadHeaders() calls itself recursively to burrow down
into the sub-directories an find all the MP3 files there. As it gets additional track
information from calls to ReadHeader(), it adds that information to the root
document, iteratively building the entire listing as a single XML document with
the schema shown in Figure 3. The ReadHeaders() method returns its result as a
string for easy transfer and processing by the client.

Figure 3. MP3HeaderReader XML Document Schema. The MP3HeaderReader class builds a list
of the MP3 tracks in the specified directory as an XML document that lists the file name, file path,
title, artist, and album for each track.

The ReadHeader() method uses another class, IP3V1Header, to do the actual file
I/O to read in the artist, title, and album information from each MP3 file. It does
this by looking at the end of the MP3 file for a header that conforms the to the IP3
Version 1 standard for embedding track information in MP3 headers. The
IP3V1Header class opens the file, locates the information, and populates the
member properties of the class with the track information and closes the file. If
this process was successful, then the ReadHeader() method simply extracts these
properties into an XML document that matches the schema expected by

Page 16 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


ReadHeaders() and returns the result as a string. The IP3V1Header class
demonstrates the declaration and use of properties in a C# class.
As you can see in Figure 4, The MP3HeaderReaderLibrary.sln solution file also
contains another project, the MP3LibClient project. This project is a simple C#
WinForms project that allows you to try out the MP3HeaderReader component. It
presents a simple form that lets you enter a path for the XML file that will be
created, a path for the component to search, a checkbox to indicate whether sub-
folders such be searched recursively, and a Search Button to invoke and call the
MP3HeaderReader component with this information. When you press Search, the
code creates an instance of the MP3HeaderReader class and passes it the
information from the controls in the form. When the ReadHeaders() method
returns, the results are simply saved to an XML file as specified in the form.

Figure 4. The Solution Explorer Window. When you open the MP3HeaderReaderLibrary.sln
solution, you will see that it contains both the MP3HeaderReaderLibrary project and the
MP3LibClient project.

To compile and run this sample, you may need to update the reference to the
MP3HeaderReaderLibrary namespace in the project references to make sure it
points to the location of that library on your system. The quickest way to do this is
to simply remove the reference and add it back in. To do this, right click on the
reference in the References node for the project as shown in Figure 3, select
Remove from the context menu. Then to add it back in, right click on the
References node itself, select Add Reference from the context menu, click on the
Projects tab in the Add References dialog, and select the
MP3HeaderReaderLibrary project in the list and press the Select button, followed
by the OK button to close the dialog.

Page 17 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


So the MP3LibClient project shows a simple .NET application using the
component within the .NET environment. There is also the MP3CPPClient project
in the download code that demonstrate instantiating and using the
MP3HeaderReader component as a COM component from C++ code. The project
was built as a simple console application in Visual C++ 6, and the code itself is no
different than you would use for any simple COM component with the same
interface. The code is shown here:

#include "stdafx. h"


#include <comdef. h>
#import <mscorli b.tlb>
#import "..\MP3Heade rReaderLibra ry\bin\Debug \MP3Lib.tlb"
no_name space
#import <msxml.dll>

int mai n(int argc, c har* argv[])


{
CoIniti alize(NULL);

IReadMP 3HeadersPtr pObj(__uuidof( MP3HeaderRea der));


_bstr_t xml = pObj->R eadHeaders(" D:\\Music\\H ard
Rock\\C reed", VARIA NT_TRUE);
MSXML::IXMLDOM DocumentPtr pDoc(__ uuidof( MSXML:: DOMDocument) );
pDoc->loadX ML(xml);
_variant _t savepat h("C:\\creed .xml");
pDoc->save( savepat h);
return 0;
}

The C++ client simply uses the Visual C++ 6 native COM compiler support and
uses #import to pull in the type library information generated by regasm.exe. It
uses the smart pointer CreateInstance() method to create the MP3HeaderReader
component, and then calls the ReadHeaders() method in the resulting interface. It
also uses the Microsoft XML parser (msxml.dll) to create an XML document,
place the XML string returned from ReadHeaders() into this To get this project to
run, you need to update the paths of the #import statements at the top of the
MP3CPPClient.cpp file, and set the paths of the output file and search directories
in the code. You will also need to run regasm.exe and gacutil.exe as previously
discussed to get the component registered with COM and allow the runtime to
locate the assembly when COM tries to instantiate it with these two commands at
a command prompt in the MP3HeaderReaderLibrary output directory:

regasm /tlb:MP3Head erReaderLibr ary.tlb MP3H eaderReaderL ibrary.dll


gacutil –i MP3He aderReaderLi brary.dll

See the Readme.txt that accompanies the download code for all the steps
necessary to compile and run the samples on your machine. To keep the code
somewhat simple for readers who are not C# experts, there is not a lot of error

Page 18 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


checking or exception handling. These should definitely be included in any
production code to make your applications bulletproof.
Summary
That about wraps up this section. I have shown you how to code, compile,
register, and run simple, non-visual COM components from C# and consume
them from unmanaged C++ clients. COM code can be pretty complex. .NET
code is very simple on its own, but the runtime and tools have to do some
Olympic class gymnastics to expose .NET components seamlessly to un-evolved
code. There is a lot more turf that you can explore to take things further to
incorporate more complex COM+ capabilities like transactions, pooling, queuing
and other features into your components. See the documentation on COM
interoperability in the .NET SDK for help on these things.
In the next section, I will show you how to build visual .NET components that
you can expose as ActiveX controls to unmanaged ActiveX control containers,
such as Microsoft Word, Internet Explorer, or any custom applications you build
with Visual C++ or Visual Basic 6.

Page 19 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


Build ActiveX Controls
With .NET
In the last section, I covered how you create a simple COM component with a
.NET class, and what is required to build, register, and run the component and
access it’s interfaces to use the functionality the component provides. In this
section, I will cover how to do similar things with a specific category of COM
components: ActiveX controls. I will cover some of the basics of ActiveX
controls as a refresher, and then will get into what is required to build an ActiveX
control using C#.
One thing to be aware of before I start is that full ActiveX interoperability is
kind of pushing the edge of the envelope for COM interoperability in .NET. Since
.NET is only in Beta 1 at the time of writing this, there are still a few rough edges
that need to be smoothed out in later releases. I will let you know what some of
these are and how you can work around them in Beta 1, if possible. But mostly I
will cover how things do and will work in the release, since this is how you
should plan on developing your code. I should note that the folks at Microsoft
have been extremely helpful in getting things working for this book and in
helping to identify what problems are Beta 1 eccentricities and how things should
work in subsequent releases.
Defining ActiveX Controls
The term ActiveX control, like many terms in the component world, has been
used to mean different things in different contexts, and has evolved somewhat
over time. The common usage of the term now is that it refers to a visual control
that can be embedded in an ActiveX Control Container. This is somewhat of a
circular definition since it depends on what is meant by an ActiveX Control
Container. You will also see the same technologies referred to as OLE Controls
and OLE Document Containers, because this was the name of the same
technologies before Microsoft coined the ActiveX term.
By ActiveX Control Container, it is generally meant that the container
application implements a number of COM interfaces that let the container
communicate with the control to handle its layout, painting, and receive events
from the control. Another distinction between different types of ActiveX controls
is whether they handle persisting the control state information to storage and
whether they provide design time support for viewing and changing properties for
the control through property pages.
Two of the most common ActiveX control containers that people are familiar
with are Visual Basic 6 Forms and Internet Explorer. Visual Basic 6 Forms
support full design time manipulation of ActiveX controls and their properties,
and changes to those properties are stored so that they can be used at runtime.
Internet Explorer is an ActiveX control container, but does not persist information
about a control to a document the way that VB6 does, nor does it support design

Page 20 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


time manipulation, so there are fewer interfaces that the control must support to
be displayed in Internet Explorer than are required for VB6 or other ActiveX
document applications. In fact, IE does not even require that an ActiveX control
be registered on the system it is run on, because it will handle the registration
automatically, so it is even easier to instantiate ActiveX controls through IE than
it is in most containers.
The interfaces that a control needs to support are dependent on what the target
client applications of that control are and what capabilities you want the control to
support. Very few programmers actually crank out the code to implement each
ActiveX interface in a control themselves, so usually you end up implementing
the interfaces that your control development tools support, and then adding others
by exception manually or through wizards. For example, in Visual C++ 6, the
ATL Control Wizard supports the creation of a variety of types of controls,
including Full and Lite Controls. MFC has an MFC ActiveX Control Wizard that
creates its default version of an ActiveX control. And Visual Basic 6 has a Project
Wizard for Creating the VB version of a default ActiveX control. Any of these
can be modified to support other features by implementing the appropriate
interface to provide the services that the interface represents.
So what you need to create an ActiveX control in .NET is a control that supports
the right interfaces that your clients will expect it to have. The .NET class that
directly implements the most common “minimum” set of ActiveX control
interfaces is the RichControl class, which I will talk about more shortly.
Table 1 shows a comparison of the different interfaces that the ActiveX controls
created through the wizards in Visual C++ 6 and Visual Basic 6 implement,
alongside the interfaces implemented by RichControl for comparison.

Interface VB MFC ATL ATL RichControl


ActiveX ActiveX Full Lite
Control Control Control Control
IConnectionPointContainer X X O O
IDataObject X X X
IOleCache X
IOleContainer X
IOleControl X X X X X
IOleInPlaceActiveObject X X X X X
IOleInPlaceFrame X
IOleInPlaceObject X X X X X
IOleInPlaceObjectWindowless X X X
IOleInPlaceUIWindow X
IOleObject X X X X X
IOleWindow X X X
IParseDisplayName X
IPerPropertyBrowsing X X
IPersist X X X X X
IPersistMemory X
IPersistPropertyBag X X X
IPersistStorage X X X X X
IPersistStreamInit X X X X X
IProvideClassInfo X X X
IProvideClassInfo2 X X
IQuickActivate X X X X

Page 21 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


IServiceProvider X
ISpecifyPropertyPages X X X
ISupportErrorInfo X X O O
IViewObject X X X X X
IViewObject2 X X X X X
IViewObjectEx X X X

Table 1. ActiveX Interfaces Implemented by Various Control Tools. The table shows the interfaces
implemented by the ActiveX control wizards provided in Visual Studio 6, and the RichControl class.
An X in the column indicates that the interface is implemented by default by the control or project
wizard code generation. An O indicates that it will be implemented if an option in the wizard is
selected to provide that support.

Controls in .NET
In the .NET world, you build visual Windows and controls using WinForms
derived classes. If you are building a standalone application in which you would
have used MFC frames or views, or VB Forms in the VS 6 world, you will now
use WinForms. If you are creating custom controls for your application or as part
of a controls library, you will use derivatives of the RichControl class, most likely
a UserControl derived class. There is an intermediate class in the hierarchy
between WinForms and RichControls, called Control, but this class only defines
the basic infrastructure behind a control – keyboard and mouse input, message
routing and security, and layout without painting. To get the full functionality
most developers would associate with a control, you will want to derive your
classes from a RichControl or one of its descendants. UserControl includes
ScrollableControl and ControlContainer in its inheritance tree, so UserControls
also support laying out other controls within their bounds and handling events
from those controls within your UserControl derived class. This is somewhat like
a Composite Control in ATL 6.
When you create a RichControl class in .NET, you have really created an
ActiveX control too, because of the interoperability support the CLR provides and
because of the interfaces that a RichControl implements. The trick to getting that
control to work with most ActiveX control containers is in getting the control
properly registered so that ActiveX containers can know of the control’s existence
and know how to instantiate the control at design and runtime. When VS.NET
gets released, there will be no additional steps required to get a .NET control
registered as an ActiveX control, other than those required for any COM object in
.NET as covered in section 1. However, until the next release, you need to do a
few things different.
If the client will be creating and interacting with the .NET control purely at
runtime (using automation interfaces) without requiring any type library
information, then the client will just need to know the path to the assembly that
contains the control and the fully qualified name of the class (with namespace)
within the assembly.
For example, in Internet Explorer, you could instantiate a .NET control using an
OBJECT tag along these lines:

Page 22 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


<OBJECT ID=”MyN etControl”
ClassID =”http:MyNetC ontrol.dll#MyComp any.MyNamspa ce.MyControl ”
height= ”300” width= ”300”>

The control would not need to be registered on the machine, but there are some
additional requirements in Beta 1 for modifying security settings on the browser
that make this a little cumbersome to put into practice. In the final release, this
should work fine with no browser modifications as long as the path and
namespace information is correct.
However, in many cases where you want to use an ActiveX control in an
application, you need to have a type library available for that control so that you
can add the control to the application at design time and so you can make the
interface connections for your application to interact with the control. To get the
control properly registered on your system, there are a couple of other things
required beyond what was required for a simple component. Some of these
requirements, such as the registration functions discussed next, may be relaxed in
future releases of .NET, but for Beta 1, this is what is required.
First, you need to add ComRegisterFunctionAttribute and
ComUnregisterFunctionAttribute to your .NET control class, and have them call
the ActiveXRegister() and ActiveXUnregister() functions, respectively. You do
this by simply adding these lines of code to the declaration of your class,
replacing the argument of the typeof() method with your class name.

[System. Runtime.Inte ropServices. ComRegisterF unction()]


private static void AxRegis ter(string regKey)
{ActiveX Register(typeof( MyContr ol));
}

[System. Runtime.Inte ropServices. ComUnregiste rFunction()]


private static void AxUnreg ister(string regKey) {
ActiveX Unregister(typeof( MyContr ol));
}

These declarations ensure that when regasm.exe or the runtime register your
control, the interfaces required for ActiveX controls (implemented in
System.Winforms.RichControl) get registered correctly as well. Again, this will
not be required in the next release, unless you want to call some control specific
code whenever the control is registered or unregistered.
The other difference in Beta 1 is that you can’t just run regasm.exe with a /tlb
switch to generate a type library that way you can with simple .NET components.
regasm.exe requires you to run tlbexp.exe on the DLL first. So after building a
control in an assembly called MyControl.dll, you would run these two commands
to get things registered correctly.

tlbexp MyControl.dl l
regasm /tlb:MyC ontrol.tlb M yControl.dll

Page 23 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


Finally, because of some Beta 1 peculiarities with shared name key files and the
registration tools, in developing ActiveX controls it is actually much easier to
simply copy the assembly into the host executable’s folder after compiling and
registering it. This way you don’t have to use the Shared Name Utility(sn.exe),
AssemblyKeyFileAttribute or the Global Assembly Cache Utility(gacutil.exe) at
all. If you want to avoid moving the DLL and type libraries around after building
your project and registering the files, then in Beta 1 you will need to rerun sn.exe
each time you need to rebuild your ActiveX control project to generate new keys
before you build, or you will end up with errors when you try to run regasm.exe to
register the resulting assembly.
As an example of the results of registration, after registering the MP3CatalogCtl
component that I will get into shortly and registering it with the tlbexp.exe and
regasm.exe tools, I could verify that all the interfaces were set up correctly by
using the OLE/COM Object Viewer that comes with Visual Studio 6. To do this, I
copy the resulting assemblies to the OLE/COM Viewer executable folder (\Visual
Studio\Common\Tools), and open the MP3CatalogCtlLib.MP3CatalogCtl entry in
the Control section of the tree listing. The results of doing this are shown in
Figure 1. You can see all the standard ActiveX interfaces implemented by
RichControl, as well as a number of others that are provided by the runtime itself.

Page 24 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


Figure 1. The MP3CatalogCtl in the OLE/COM Viewer. In addition to the normal interfaces
required to register a COM component as an ActiveX control, a .NET control also registers a
number of other interfaces that result from the inherited interfaces on the control in the .NET
Framework class hierarchy. These interfaces are seen prefixed by an underscore in the figure.

Build Your First .NET ActiveX Control


If you want to get right down to the basics and create a minimal ActiveX control,
you can just use the VS.NET C# project wizard to create a new Windows Control
Library project. Select the control in the Design tab in the VS.NET editor, and
change its BackColor property to something other than gray in the Properties
window so that it will show up better embedded in a dialog. Insert the code from
earlier for the register and unregister functions, changing “MyControl” in the code
to the name of your class. Then build the solution, open a command prompt in the
project’s \bin\debug folder, and run tlbexp.exe and regasm.exe as described
earlier on the assembly.
Now build a simple client by creating an MFC Executable through the project
wizard in VC6, and select a default MFC Dialog as the project type and press
Finish in the AppWizard on the first step to create the project. Copy the assembly
(i.e. MyControl.dll) into the folder that was created for your MFC project. Now
right click in the dialog editor on the dialog itself, and select “Insert ActiveX
Control”. Find the ProgID of the control you created in the listing (it will be of the
form ProjectName.ClassName). Compile and run the MFC project, and you
should see your first .NET ActiveX control in action.
Naturally, this control is not too exciting to use, so lets talk about one that is a
little more interesting. In the download code that accompanies this article, there is
a C# project that builds on the code I developed for Section 1. As you will recall,
the download code has a C# component called MP3HeaderReader that I exposed
as a COM component to read MP3 header information from MP3 files on your
system. For this section, I reused that component from within .NET by
implementing another C# component, MP3CatalogCtl, that is a UserControl
derived class that presents a tabular view of the MP3 header information read
using the MP3HeaderReader component. MP3CatalogCtl takes the XML stream
passed back from MP3HeaderReader and binds it to a DataGrid – a very powerful
control included in the .NET Framework – to display the dataset in a tabular form
that is easy to manipulate. Figure 2 shows the containment relationship between
the client application that hosts MP3CatalogCtl as an ActiveX control, the
MP3HeaderReader component that it uses to read the information from the file
system.

Page 25 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


Figure 2. The MP3CatalogCtl Containment Relationship. An ActiveX control container client will
host the MP3CatalogCtl, which instantiates an MP3HeaderReader component to do the actual
reading from the file system. In reality, the MP3CatalogCtl and MP3HeaderReaders will be running
in the managed environment of the CLR, and a COM Callable Wrapper will be providing the
exposed ActiveX interfaces that the client uses.

The MP3CatalogCtl class implements a public interface named


ICatalogMP3Files that is defined as follows:

[Guid("6E1B6 D1F-878E-4f1 a-A89B-6F273 A73D247")]


public interface IC atalogMP3Fil es
{
void SetPath (String pat h);
String Artist { get;}
String Album {get;}
String Title {get;}
String FilePat h {get;}
String FileNam e {get;}
}

This interface simply allows the client to specify the path from which to catalog
the MP3 files using the SetPath() method. It also provides methods to retrieve the
track information for the currently selected track in the DataGrid. Note that I also
explicitly set the interface GUID using GuidAttribute from the
System.Runtime.InteropServices namespace. This is a good idea so you know
explicitly what the GUID of the interface will be when it is registered as a COM
interface by the runtime and the .NET Framework tools.
The SetPath() method implementation in the MP3CatalogCtl class creates an
instance of an MP3HeaderReader component, has it read all the MP3 files on the
path and return the results as an XML document string. The SetPath() method
then converts that string into a StringReader stream, and uses the
DataSet.ReadXml() method to read the results into a DataSet that is a private field

Page 26 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


in the control and holds the information that the DataGrid displays. Finally, it
calls the DataGrid.SetDataBinding() method to update the contents and display of
the DataGrid with the results just read in. This method is shown here:

// private field for t he Data


private DataSet dsTrack s;

/// Imp lements the ICatalogMP3F iles interfa ce method


public void SetPath (string pat h)
{
IRead MP3Headers r eader = new MP3Head erReader();
bool recurse = true;
string results = reader. ReadHeaders( path,re curse);
StringR eader sr = ne w StringR eader(result s);
dsTrack s.ReadXm l(sr);
dataG rid1.SetDat aBinding(dsTrack s, "track");
}

The download code that accompanies this book contains a Visual C++ 6 MFC
client application that consists of a simple dialog that hosts the MP3CatalogCtl
component as an ActiveX control. You can see this application in action in Figure
3. The MFC client was built by using the Insert ActiveX Control… command in
the dialog editor to insert the control just like you would any other ActiveX
control – by selecting its name based on the registered ActiveX controls on the
system. I then used the #import VC++ native COM compiler support to import
the type library for the MP3CatalogCtlLib.tlb type library, so that I could use the
ICatalogMP3Files interface to interact with the control. See the MFCMP3Client
project source code for the details. There is also a Readme.txt file that
accompanies the project for Part 2 that will talk you through building, configuring
and running the samples.

Page 27 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


Figure 3. The MP3CatalogCtl Control Hosted in an MFC Dialog. Pressing the Browse button
and selecting a folder will call the SetPath() method with that folder path, and the grid will populate
with the MP3 files in that folder and its subfolders. Pressing the Get Track Info button will call the
property methods in the ICatalogMP3Files interface to retrieve and display the track info as shown.

Getting Interactive – Sourcing Control Events


One of the most useful features of ActiveX control is the ability to embed them in
a hosting application, and have that application be notified whenever an
interesting event occurs within the control, such as when the user clicks on some
content within it. Unfortunately, this is one of those rough edges in Beta 1 that
should be ironed out in a later release. Events just don’t work from visual controls
in Beta 1. Rather than going into detail on the peculiarities of Beta 1, I will cover
how events work in C# and how you will be able to hook them up as COM events
when VS.NET gets released. You will just need to realize that this is to prepare
you for the release version – don’t waste your time trying this in Beta 1.
COM events are provided through the mechanism of Connection Points, and
depending on what your tool of choice is for ActiveX controls in Visual Studio 6,
a lot of the nitty gritty details of defining and handling those events is made easier
by the development environment. For example, in VB6, if you embed an ActiveX
control in a form, and that control sources events, when you insert that control in
your form through the Components menu, the events drop down in the code
window automatically lists the events available for the control. All you have to do

Page 28 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


to handle those events is select them in the list and add any desired code in the
handler that VB creates for you.
In MFC, there is a little more work to be done. If the client is a dialog and you
insert the ActiveX control using the dialog editor, then the control’s events show
up in ClassView automatically as available Message Maps. If you are spinning
your own window using a CWnd derived class, you have to hook things up
yourself using the DECLARE_EVENTSINK_MAP() and related macros to
handle the events as dispatch events. You could also wire things up at an even
lower level by requesting an IConnectionPointContainer interface from the
control and calling FindConnectionPoint() on that interface to get an
IConnectionPoint interface for the event interface of your choice, and passing the
control a sink interface on which it will call the event methods. This is kind of the
graduate level approach to COM event handling, and I don’t have room to go into
it here. See the resources at the end for some good books that will help you
understand this approach.
The good news is that when Visual Studio.NET gets released and you expose
your .NET controls as ActiveX controls, they should plug in to unmanaged client
applications just as seamlessly as if they had been developed in any other ActiveX
control development tool. However, you will still need to understand how to
source events from a .NET class in order for those events to get exposed to COM
clients as COM controls. Although the underlying mechanisms for events in .NET
are common for all .NET languages, the syntax naturally varies depending on
your development language of choice. I cover how to source events in C# next.
C# Events
In .NET, events are declared and handled a lot differently than in COM or other
event models. To declare events in C#, you use a combination of a delegate and
an event declaration. A delegate is basically a way of declaring what the signature
(method name, return type, and parameters) for an event will be. Although a
delegate looks like a function declared at global scope in C#, this is not what it
really is, because there is no such thing as a global function in C#. You will also
see them referred to as “safe function pointers”, which is a good way to
understand the role that they play, but once again, function pointers are a no-no in
C#, so they are not really that either.
You can declare a delegate in C# with this syntax:

namespa ce MyContr ols


{
...
public delegate voi d DoSomet hingDelegate ();
public class MyContr ol {
public event DoSomet hingDelegate doSomet hing;
}
...
}

Page 29 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


The delegate declaration syntax in C# is actually a shortcut way of defining a
class derived from System.Delegate. When your code gets compiled, a class will
get declared in the Intermediate Language (IL) with metadata indicating that it
does not contain an implementation. The generated delegate class will have a
constructor and a method called Invoke(). The constructor takes a reference to the
object that is the target of the delegate, and an integer with the address of the
function that will handle the call. The Invoke() method will have the return type
and parameters of the delegate itself. This is all handled for you by the compiler
and the runtime, but it is good to know what is going on under the covers to help
understand what a delegate is doing for you. The best way to see what is going on
is to run the IL Disassembler tool on the assembly in question. Figure 4 shows the
MyControls namespace in IL DASM. You can see that the delegate shows up as a
full fledged class with constructor (ctor()) and Invoke() methods.

Figure 4. The IL DASM Tool. This tool allows you to see what is going on at the IL level. Here you
can see that the resulting IL from the previous example code results in a MyControl class and a
DoSomethingDelegate class. The event shows up with add_ and remove_ methods that are called
when the += and -= operators are used to hook up the event. Invoke() is called to fire the event.

However, delegates are only half of the event equation in C#. To use them, you
also need to declare and invoke a C# event as part of the class that is the source of
the event. To declare an event, you simply use syntax like this line from the
preceding code:

public event DoSomet hingDelegate doSomet hing;

If you think of delegates as the class that they end up when compiled, this syntax
is a little easier to digest. It is kind of like you are just declaring a member
instance of the delegate class and giving it a name. But that is a simplification of
what is really going on.

Page 30 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


To source the event in your code, you call the event as if it were a function,
passing any parameters that are declared in the delegate. What happens when you
do this is that the compiler translates the call into a call on the Invoke() method of
the Delegate class, passing the parameters to that method. If no clients have
indicated that they are interested in receiving the event by adding themselves to
the event’s subscriber list, then the event will be equal to null. You need to check
this before calling the event so that you don’t end up with a null reference
exception. To publish an event, simply call the event with the proper arguments.
In the case of our simple doSomething event, you would just call it as if it were a
function with no parameters:

class MyContr ol {
...
void Somethi ngHappened()
{
if (doSomet hing != null )
doSomet hing();
}

Clients in C# add themselves as a subscriber to an event by using the +=


operator on the event itself. For example, a client of the MyControl class could
subscribe to the doSomething event with code like the following. Note that the
method that you pass in to the delegate as the handler needs to have the exact
same signature as the delegate declaration itself:

MyContr ol acontro l = New MyContr ol();


acontro l.doSomethin g += new DoSomet hingDelegate (OnSomet hingDone);
...
void OnSomet hingDone() { //handle th e event }

When defining events for .NET clients, it is a good idea to pass some parameters
with the event that allow the subscriber to discern a little more information than
just that an event occurred. A standard convention is to pass an object reference as
the first parameter, and a class as the second parameter that has any additional
information about the event that you want to provide. Usually, you should use the
EventArgs Framework class as a base for this second parameter class, although
that is not a requirement. However, if you are sourcing events to COM clients,
you will want to make sure the arguments in your events are limited to COM
compatible types, specifically oleautomation compatible types, since most
ActiveX control containers handles events as dispatch interface events.
Sourcing COM Events
To get a C# event exposed as a COM event will be a pretty straightforward thing
in the release version of VS.NET. You can base the COM event either on an event
interface defined within a .NET assembly, or you can base it on an event interface
defined in an existing COM type library. Defining the interface from within your
.NET code gives you a little more control over what is going on because you can

Page 31 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


explicitly set the way the interface will be exposed through the rich set of COM
interoperability attributes available. I will cover how to do this shortly.
If you are implementing an event that is predefined in an existing COM type
library, you can just import its definition into an assembly and use that definition
to source the event. All of the identifiers, interfaces, method names, and their
arguments should be retained. However, if there are name clashes when the type
library is imported, there is a set of rules on the way the names will be
transformed based on the interface name. See the .NET Framework
interoperability documentation for details. I will cover how to import a type
library definition of an event interface in a bit.
If you want to expose events by defining the event interface in your .NET
assembly, you need to define an interface with a method for each event that you
want to expose, and each method’s signature needs to match the signature of the
delegate that it corresponds to. For example, if you want to expose two events –
one called GridDoubleClicked() that takes no parameters and just tells the client
that the DataGrid was double clicked, and another called RowSelected() that
passes an integer parameter describing what row was selected in the DataGrid
control – then you would need to define code something like the code shown here:

namespa ce MP3Catalo gCtlLib


{
using System. Runtime.Inte ropServices;
[Interfa ceTypeAttrib ute(ComInte rfaceType.In terfaceIsIDi spatch)]
public interface IDoGrid Events
{
void GridDou bleClicked() ;
void RowSele cted(int rowNo);
}

public interface IC atalogMP3Fil es


{
...
}

public delegate voi d GridDou bleClickedDe legate();


public delegate voi d RowSele ctedDelegate (int rowNo);

[ComSour ceInterfaces (“MP3Ca talogCtlLib. IDoGridEvent s”),


HasDefa ultInterface ()]
class M P3CatalogCtl : UserCon trol, ICatal ogMP3Files
{
...
public event GridDou bleClickedDe legate GridDou bleClicked;
public event RoSelec tedDelegate RowSele cted;
...

Page 32 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


OnDoubl eClick()
{
if (GridDou bleClicked ! = null)
GridDou bleClicked();
}
OnNavig ate()
{
// G e t t h e s e l e c t e d r o w n u m b e r
if (RowSele cted != null )
RowSele cted(rowNo);
}
} // class
} // namespa ce

Let’s walk through this a bit at a time so that you can understand what is going
on. First you should note the definition of the delegates and events in the class.
Since they are marked as public, they will by default be exposed in the exported
type library for the class as a coclass and a corresponding interface when you use
tlbexp.exe to generate a type library. Since you will not be using these directly,
you can just ignore them. If you want to suppress the visibility of the delegates in
the type library, you can use the ComVisibleAttribute to make them invisible to
COM clients.
As you can see, to get everything to work correctly, your delegates should have
a parameter signature that matches the exposed event interface methods, and the
corresponding event names should match the event interface method names.
Somewhere in your class, you will need to source the events whenever the
appropriate event occurs. You do this simply by calling the event by name with
the correct parameters of the delegate that defines the event. You need to check
the event to see if it is null as mentioned earlier so that you don’t try to call the
event if there are no subscribers, which would result in an exception.
You will also notice some important attributes in the preceding code. The
InterfaceIsDispatch attribute declares the event interface as a dispinterface, which
is what most ActiveX controls are prepared to handle. You could also declare a
separate interface with the InterfaceIsDual attribute if you wanted COM clients
capable of using IUnknown derived connection points to be able to achieve the
improved performance of a non-dispatch call. As mentioned earlier, in Beta 1, the
disinterface approach will work for non-visual component, but the dual approach
will not. Neither approach works for visual ActiveX controls in .NET Beta 1.
The key to getting your interface exposed as a COM source (event) interface is
ComSourceInterfacesAttribute. You can list out multiple events in this attribute in
a null separated list if they are defined in a .NET assembly. For example, if I had
defined a dual interface for the same events in the preceding code name
IDoDualGridEvents, then you would use ComSourceInterfacesAttribute as
follows:

Page 33 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


[ComSour ceInterfaces (“MP3Ca talogCtlLib. IDoGridEvent s\0MP3Catalo gC
tlLib.I DoDualInterf aceEvents”)]

If you were using an external type library, you would first need to import the
type library into a .NET assembly. You can do this at the command line using the
Type Library Import Tool (tlbimp.exe), or you can let the VS.NET IDE do it for
you automatically. To do the latter, just right click on the References node for the
project in Solution Explorer, select Add Reference..., select the COM tab in the
Add Reference dialog, and select the type library that you want to import. The
type library will need to be registered on your system to show up in the list.
VS.NET will run tlbimp.exe for you, and will add a reference to the resulting
assembly to your project. If you run tlbimp.exe yourself from the command
prompt, you will need to add a Reference to the resulting assembly after you are
done, by selecting Browse… in the .NET Framework tab of the Add Reference
dialog and navigating to the folder where you tlbimp.exe output.
Once the reference is set to the assembly containing the type library’s types, you
again just use the ComSourceInterfaceAttribute to declare the interface within that
assembly that the .NET class will use to source the events. You do this with this
syntax, specifying the assembly name and interface, followed by the assembly
name again.

[ComSour ceInterfaces (“MYTYPEL IBLib.MySour ceInterface,


MYTYPEL IBLib”)]

In this line, MYTYPELIBLib is the name of the namespace in the assembly


generated by the running tlbimp.exe tool on a typelib named MyTypeLib.tlb or by
adding a reference to the typelib directly in the IDE. MySourceInterface is the
name of the event interface defined in that type library. Just like with the
internally defined event interface, the event names and delegate signatures for the
events need to match the name and signature of the corresponding event interface
method in the type library.
Summary
In this section, I have covered how to expose a .NET RichControl derived class as
an ActiveX control to unmanaged client applications. The steps to do so are pretty
straightforward, and will be even more straightforward in the next release when
ComRegisterFunctionAttribute is no longer a requirement. I covered how to
declare and source events from C# in general, and then covered the specifics of
how those events can be exposed to COM clients. Unfortunately, the events
functionality is not working correctly in Beta 1, so I had to base this information
on how things are supposed to work, instead of how it does work. Keep in mind
that some of these requirements may change by the time we get to the final
release of VS.NET. However, the information presented here should work fine
based on conversations with the product managers for ActiveX controls and
Interoperability in .NET at Microsoft.
In the next section I will be turning the tables and covering how to consume
COM components and ActiveX controls from within a .NET application. This is
Page 34 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om
probably an even more likely scenario as companies migrate their code to .NET.
New applications will start to be built on .NET, but there is a lot of well tested
and proven functionality out there packaged as COM components and ActiveX
controls that you will probably want to use for some time until it becomes
necessary to repackage that functionality as .NET components.

Page 35 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


Use COM Components
and ActiveX Controls
in .NET
In the last two sections, I have focused on how to build COM components and
ActiveX controls in .NET. In the process, I covered the steps required to expose
your .NET components to unmanaged clients. I also covered a lot of the
infrastructure that is built into the .NET framework to support COM
interoperability. There may be many scenarios where you will want to build a
component or control in .NET and expose it to unmanaged applications as a COM
component or control in this way. However, as you migrate your applications to
.NET, an even more likely scenario will be where you want to reuse some chunk
of functionality packaged as a COM component or ActiveX control without
having to rebuild that component to run in the managed environment. Luckily,
this part of COM interoperability in .NET is even easier than the exposing
components as COM controls. In this section, I will cover how to integrate COM
components and controls in your .NET applications, and will cover the
architectural support and tools that surround doing this.
.NET Runtime Support for COM Objects
In the preceding sections, I explained how the .NET runtime uses a COM Callable
Wrapper (CCW) to allow unmanaged clients to use .NET components just like
they would any other COM component. A similar mechanism exists to act as a
bridge between COM components in the .NET world: the Runtime Callable
Wrapper (RCW).
Whenever a .NET application or component interacts with a COM component,
the talking is really being done through a RCW. The RCW handles instantiating
the COM component, managing the reference counts on a COM object, making
interface method calls on the object, marshaling parameters and return values for
those method calls, any error handling, and finally, releasing the COM object
when the .NET client is done with it. A .NET client creates one RCW for each
COM object that is in use. Even if the .NET client holds multiple references on
the COM object, those references will all be to the same RCW object. The RCW
keeps track of the reference counts on the object, and calls AddRef() or Release()
as appropriate based on how many active interface references are being held by
.NET clients.
The RCW is a .NET object, and will be garbage collected when all of the
references are released to the COM object it manages. Because of the non-
deterministic nature of garbage collection, there are some additional
considerations here in terms of the release of COM objects that can be important
if the COM objects are resource intensive, which I will cover in more detail a
little later. Figure 1 depicts the way the RCW acts as a bridge between the COM

Page 36 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


object and the .NET client. An RCW can be created in an early or late bound
fashion as I will cover shortly, so keep in mind that when I discuss a RCW, it
could be based on information in an assembly created by importing the COM type
library information either at design time or at runtime.

Figure 1. The Runtime Callable Wrapper (RCW) Does the Talking. When .NET components or
applications use COM objects, the runtime creates a RCW to handle communications between the
managed and unmanaged world. The references will appear like references to normal .NET types
to the .NET client, and the RCW will handle instantiation and calling the methods on the
appropriate COM interface. The RCW supports calling both early and late binding through
IUnknown derived and IDispatch derived interfaces, respectively.

Because the runtime handles all the COM specifics for you through the RCW,
you do not have to do anything special in your .NET code to instantiate and call
methods for the COM component. All you need to do is use the .NET Type
Library Import tool (tlbimp.exe) to create a .NET assembly that represents the
types in the COM library you want to use, reference that assembly in your project,
and then use the types defined in the generated assembly as if they were defined
and built as .NET components. In fact this gets even easier, if you are using
Visual Studio.NET. All you have to do is add a reference in your VS.NET project
to the COM type library, and the IDE will run the tlbimp.exe tool for you, as well
as add a reference to the resulting assembly to your .NET project. I will step
through the mechanics of this shortly.
The .NET runtime supports both early and late bound activation of COM
objects. Early bound activation requires the importing of type information from
the COM type library as mentioned before so that the compiler knows all about
the COM component’s interface methods, properties, and events. Using early
bound activation, the code you write to use a COM object will be no different
than if that object were defined in a different .NET assembly as a .NET
component. Late bound activation is supported for COM objects that implement
IDispatch derived interfaces, allowing you to use COM objects without needing to
import their type information at build time. I will get into examples of creating

Page 37 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


and using both early and late bound COM components shortly, and you will see
just how simple it is to use COM components in your .NET applications.
Bind Early to COM Components
Let’s step through the process of using a COM component in a C# application
with early binding. The first step is to write a C# application in which you want to
use some functionality that resides in a COM component. For this part, I
developed a simple C# Windows Application that uses the Windows Media
Player ActiveX control and the MP3CatalogCtl developed in Part 2 to implement
a simple MP3 player application. For this application I reused MP3CatalogCtl
directly as a .NET component. I made some minor modifications to the code to
support some additional functionality needed by this application, and put that
functionality into a separate interface called ICatalogMP3FilesEx. This interface
and its implementation just add Next() and Back() functionality to let the user
forward and backward in the track list. The new version of the MP3CatalogCtl is
contained in the download code for Part 3.
So the first thing you need to do to create the CSMP3PlayerApp application is to
create a C# Windows Application project, and add buttons for Load, Play, Stop,
Next, and Back, as well as a checkbox to enable random playback order down the
left hand side of the window, as shown in Figure 2. Stretch the window so that it
is about 600 X 400 pixels in dimension. Next, add an instance of the
MP3CatalogCtl to the form. To do this, go to the Components section of the
Toolbox, right click and select Customize Toolbox.

Figure 2. Rock On With the CSMP3Player. The sample application for this section demonstrates
how to integrate an ActiveX control into a Windows Form application. The Windows Media Player
control is inserted as an invisible control, and is used to provide MP3 media file playback.

Page 38 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


In the Customize Toolbox dialog, select the .NET Framework Components Tab,
press the Browse button, and navigate to the MP3CatalogCtlLib.dll assembly
location and select it. Once you press the Open button to accept your selection in
that dialog, the MP3CatalogCtl will be selected in the list of .NET Framework
Components as shown in Figure 3. Just select OK to get out of the Customize
Toolbox dialog, and an icon will be added to the Components list in the Toolbar
for MP3CatalogCtl. Now you can insert it just like you would any other control in
a WinForm, by selecting it in the list and drawing it in the form.

Figure 3. Customize the Toolbox. You can add additional .NET components and COM controls to
the toolbox and drag and drop them in your Windows Forms through the Customize Toolbox
command.

So far, this is all just basic Visual Studio.NET and WinForms layout. Now for
the focus of this article – adding a COM component to the application. Well,
hopefully you haven’t already forgotten what you did in the last paragraph to add
a .NET control, because through the magic of Visual Studio.NET, the process of
inserting an ActiveX control is almost exactly the same. The only difference is
that instead of going to the .NET Framework Components tab in the Customize
Toolbar dialog, you stay right on the COM Controls tab, find the control you want
in the list, check it, and it will be added to the toolbox. From there, you insert it in
the form just like any other control.

Page 39 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


When you insert an ActiveX control in a WinForm though, the IDE does a little
extra magic on your behalf. The IDE will know that it is an ActiveX control that
you are inserting, and will check the References for the project. If there is not
already a reference to an Assembly for the control generated by tlbimp.exe, the
IDE will run tlbimp.exe for you on the ActiveX control’s type library, and will
generate a .NET assembly for the library, and add it to your references. From then
on, you (and the IDE form designer) just use the created control class as if it was a
.NET class.
So for the sample application, follow the steps above to customize the toolbox,
and scroll down in the COM controls list until you find the Windows Media
Player control. Check the box next to it and click OK. Then click on the resulting
icon in the Toolbar and draw in the control somewhere on the form. The size is
not important, because the next thing to do is to set the control’s Visible property
to false in the Properties window with the control selected in the form. For this
simple application, I chose to just use the playback capability of the control, so I
don’t want the control visible. You can certainly experiment and leave it visible if
you want to use its controls instead of your own.
Once you have wired up the control buttons as they are in the download code,
you should be ready to run. The way it works is simple, when you press the Load
button, you are presented with a standard Browse For Folder dialog that lets you
navigate to where your files are stored on your system. When you select a folder,
the folder path is handed to the MP3CatalogCtl, and it populates itself just as it
did for the last part, with all the track information for MP3 files in those folders.
Now when you press the play button, the application just requests the currently
selected track from the MP3CatalogCtl and sets the FileName property on the
axMediaPlayer1 control.

protect ed void Play_Cl ick (object sender, System. EventArgs e)


{
ICatalo gMP3Files ca t = mP3Catal ogCtl1;
string fpath = cat.Fil ePath + "\\" + cat.Fil eName;
axMedia Player1.FileNa me = fpath;
}

The axMediaPlayer1 control instance was created by the WinForms designer


when you inserted the control. You can see here that using the control is as simple
as using any .NET component. You can set or get the properties with the same
syntax, and you can call methods the same way you would if it was a .NET
component. The RCW will handle marshaling method parameters and return
values, and will handle any necessary type conversions. If a COM method call
returns an error HRESULT, an Exception will be raised. See the .NET
documentation for the mapping between COM error HRESULTS and .NET
Exceptions, and the discussion later in this section on error handling. I should
note here that when you compile the application in Beta 1, you will get some
warnings about using the FolderBrowser class. The fate of this class is yet to be
determined in future releases, but there is still clearly a need for this folder

Page 40 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


selection functionality. Microsoft will either retain the functionality in some form
in the framework, or provide it as a sample class.
So far we have only talked about ActiveX controls. What about non-visual
COM components, you might be wondering? Well, to use a COM component, the
process is virtually the same, although you don’t have to customize the toolbox to
use it. In the case of simple COM components, just add a reference to the COM
type library for the component by selecting Add Reference from the References
node of the Project in Solution Explorer. When you do this, the IDE again runs
tlbimp.exe to generate an assembly for the type library, and from there you can
just use the component as if it were a .NET component, as discussed before.
You might notice that when you insert the Media Player control using the
process described earlier, two references are added to the project, one to
AxMediaPlayer.dll and one to MediaPlayer.dll. These are both generated by the
IDE when you add the component to the form. The MediaPlayer.dll assembly is
generated to contain the COM type definitions for the control, and the
AxMediaPlayer.dll contains just the ActiveX default interface and event
definitions.
Bind Late If You Choose
.NET also supports late binding to COM objects. Late binding allows you to use
an object at runtime without any compile time information about its properties
and methods. In practice, you usually need to know something about the
component in order to write the client code. But you do not need to have a type
library available for a component that supports late binding to be able to use it.
Late binding in .NET Beta 1 is a little different and a little harder than it will be
in the release version. For Beta 1, you have to use Reflection to call methods and
get or set properties on a late bound object. In Beta 2 and beyond, you will be able
to call the methods and set the properties directly, similar to how it is done in
scripting languages today. You can still use the Reflection route in later releases,
but it is much more painful, so why bother.
The way you create a late bound COM object is by using the Type class and the
methods GetTypeFromProgID() or GetTypeFromCLSID(). You then map that
type to an object using an Activator so that you can use the object to invoke
methods against.
In the download code, there are two projects that demonstrate late binding in
.NET Beta 1. There is a simple ATL COM component project for VC6 called
MyFinancialCalculatorSvr and a C# project called LateBoundClient that contains
a Windows Form project that allows you enter the values to pass to the
MyFinancialCalculator component. MyFinancialCalculator has a single method
on it called CalculateInterest.

HRESULT Calcula teInterest([in] d ouble amount , [in] int numYear s,


[in] do uble annualR ate, [out, retval] double* int erest);

This method takes a dollar amount, the number of years to compound interest,
the interest rate, and it returns the earned interest over that period with annual

Page 41 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


compounding. This method is defined on a dual interface, so it can be exposed to
late bound clients through automation.
The LateBoundClient form calls this method when you press the Calculate
button. This calls a method that instantiates the COM component in a late bound
fashion and calls its methods using reflection. The first thing it does is instantiate
the component.
protect ed void Calcula tebtn_Click (object send er, System. EventArgs
e)
{
// error c hecking skip ped for simp licity >:-)
double damount = amount. Text.ToDoubl e();
double drate = rate.Te xt.ToDouble( )/100;
int iyears = years.Text .ToInt32();

// Cr eate a late bound instan ce of the MyFinan cialCalculat or


compone nt
Type t;
Objec t calc;
t = Type.Ge tTypeFromPro gID(
"MyFinan cialCalculat orSvr.MyFina ncialCalcula tor");
calc = Activat or.CreateIns tance(t);
...

You can see that the process is simple. You use the Type class to get the runtime
class from a ProgID or CLSID. Then you use the Activator class to create a
runtime instance of the object. This part will remain the same in Beta 2 and
beyond. The thing that will be different in Beta 2 and later is how you call the
methods and get/set properties. In Beta 1, I had to do this:
// Ge t the parame ters into an Object arra y
Object[ ] parms = new Object[ 3];
parms[0 ] = damount ;
parms[1 ] = iyears;
parms[2 ] = drate;

// In voke the met hod


double intamou nt = (double ) t.Invok eMember(
"Calcula teInterest", Binding Flags.Invoke Method,
null, c alc, parms);
Decim al d = intamou nt.ToDeci mal();
interes t.Text = d.ToStr ing();
}

The first step is to package any parameters that you need to pass to the method
in an array of Objects. Next, you use the InvokeMember() function of the Type
class to call the desired method by name, passing in the object that you want to
invoke the method on, and the parameter array. InvokeMember() will return any
[out, retval] parameter on the method as the return type from the call. So in the
sample, I just cast this returned Object to a double, since that is the actual type.
The last two lines just manipulate it to display the result.
Page 42 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om
If you wanted to accomplish the same thing in later releases, this would be as
simple as calling the method on the object, as follows:

double intamou nt = calc.Ca lculateInter est(damount , iyears,


drate);

What’s Going On Behind the Curtain


When you run the Type Library Importer tool, tlbimp.exe, or the IDE runs it for
you when you add a reference to a COM type library, the tool reads in the type
library and generates a .NET assembly. In the generated assembly, the tool creates
types that will be used by the RCW to translate calls between the .NET type and
the COM type running in the unmanaged environment. A similar process occurs
when a late bound COM object is instantiated by .NET code. In most cases, things
map over exactly as you would expect – the library name becomes the namespace,
a coclass becomes a .NET class of the same name, a COM interface becomes a
.NET interface of the same name, and methods and properties on the interfaces
get mapped over to their respective interface or class with the same name.
IUnknown and IDispatch methods of an interface get stripped out in the
conversion process, so they won’t clutter up the interface definitions.
The type library conversion process has to do a number of things to get
everything translated over correctly. First, the process must convert COM types
and declarations into .NET compatible types. For example, a BSTR will get
mapped to a System.String, and a Variant of type VT_BOOL will get mapped
into a System.Boolean type. The tool also has to convert [out, retval] method
parameters to return values of the appropriate type. At runtime, the CLR must
map failure HRESULTs into .NET exceptions. The tool and runtime must also
handle any naming collisions that occur. The .NET documentation goes into a
great deal of detail outlining all of these conversions, so I won’t go into that in
detail here.
One of the best ways to understand what conversions are taking place is to run
the IL Disassembler (ildasm.exe) on the assembly generated by the tlbimp.exe
tool. For example, if I ran tlbimp.exe on the MyFinancialCalculatorSvr.dll library
that I created for the late bound example, and then ran ildasm.exe on the resulting
assembly, I get the information shown in Figure 4.

Page 43 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


Figure 4. See Through the Smoke and Mirrors. The IL Disassembler will show you the internals
of any .NET assembly, including one generated by the Type Library Importer. This is an excellent
way to see what conversions have been made and to understand the conversions being made from
COM types to .NET assembly types.

Explicitly Release Your COM Objects


As mentioned earlier, a RCW is a managed type, and will not be garbage
collected until all references to it are released or go out of scope. Until the RCW
is garbage collected, it will continue to hold references on the COM object it
manages. When the RCW gets garbage collected, it releases all its references to
the COM object, allowing the COM runtime to destroy the COM object, which
will also release any resources that the COM object was holding onto, such as
database connections, memory, files, or network resources. However, since
garbage collection is controlled by the runtime and is only performed as
necessary, it could potentially be a long time after you are done using a COM
object that its RCW and corresponding references to the COM object get released.
The upside to this is that when using COM objects in .NET, you do not have to
worry about reference counting and releasing your objects if you don’t want to,
just like with other .NET types. You can just create them, use them, and let them
die when the runtime does its housekeeping. But in many cases, you will not want
to wait until the runtime gets around to performing a garbage collection run to
release these resources if you know you are done using the COM objects that hold
them. To get around this problem, there is a way you can force the runtime to
release the COM references a RCW holds with the Marshal.ReleaseComObject()
static method. To do this, you simply pass the RCW to this method, and all
remaining references to the COM object will be released so the COM object can

Page 44 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


shut down and release any resources it is holding. This code demonstrates how
you would do this in C#.

// DBConne ctionManager is a COM ob ject


DBConne ctionManager dbcm = new DBConne ctionManager ();
dbcm.Manage Connections( );
// done wi th dbcm
Marshal .ReleaseComO bject(dbcm);

There are some additional considerations in terms of where you call


ReleaseComObject() from and with what RCW reference. See the “Maintaining
Object Lifetime” topic in the COM Interoperability Specification documentation
for the details.

Write Robust Code


When using COM objects in .NET, there are a number of things that could go
wrong. In .NET, erroneous conditions manifest themselves as exceptions. This
holds true for COM imported types as well. COM errors are indicated by the
severity bit set to 1 in an HRESULT returned from a COM interface method call,
which translates into a failure HRESULT. In .NET, imported COM types will
raise exceptions that correspond to the specific failure HRESULT returned. Since
there are a great many common error HRESULTS defined for COM, the list of
mapped exceptions is quite large. To look up the exceptions corresponding to
your favorite HRESULTS, see the information on the ISupportErrorInfo interface
in the “Consuming Selected Interfaces” topic in the COM Interoperability
Specification in the .NET SDK documentation. Success HRESULTS do not get
passed through, unless you take some additional steps to get them passed through
as a long return type. Again, see the docs for the details on how to go about this.
As a result of the fact that the wrapped COM objects may raise exceptions when
they are not happy, you should wrap all your method calls to COM objects in
appropriate exception handling blocks, such as a try/catch block in C# or C++. If
the COM object you are using is well documented enough that you know of
specific failure HRESULTs that the method calls might raise, you should look up
the corresponding .NET exceptions, and have exception handlers specifically for
those exceptions. You should always have a general handler that catches any
Exception derived class to handle the general case. In addition to just catching the
exception, if the COM object implements the ISupportErrorInfo and IErrorInfo
interfaces, then when the exception occurs, the runtime will inject the information
obtained from these interfaces into the Exception object, so you should check for
additional information in the Message, Source, and HelpLink fields of the
Exception object when handling it.
In the normal COM world, you make a call to QueryInterface() to see if an
object supports a particular interface. In the .NET world, if you know that the
object you are using implements a particular interface, you can simply cast to that
interface:

Page 45 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


MyObjec t obj = n ew MyObjec t();
IMyObje ct2 itf = ( IMyObject2)obj;

However, if the object doesn’t support the interface, this will throw an
InvalidCastException, which you will need to be prepared to handle. If there is a
chance the object will not support the interface, such as if your application
supports multiple versions of components with different interface support, then
there is a more robust way to handle things. What you can do is use the
Type.IsInstanceOfType() method to check whether the object supports an
interface before attempting to cast to that interface type. This code shows how
you would do this:

MyObjec t obj = n ew MyObjec t();


IMyObje ct2 itf;
if (itf.Get Type().IsInsta nceOfType(obj)) {
itf = ( IMyObject2) obj;
// use itf
}

Handle COM Events


As I mentioned in the last section, bridging the gap between COM events and
.NET events is Interop on steroids, and Beta 1 has not had its daily supplement
yet. Sinking events from a COM object in .NET will be just as easy as sinking
.NET events from another .NET object when .NET gets released. In Beta 1, things
are not quite working right yet, and it is probably not worth the time to even try.
In Beta 2, things will most likely be much improved and much closer to how
things will work in the release, so I will briefly discuss how things will work.
COM events were covered in some detail in the last section. COM events rely
on the connection point mechanism, where the COM component has to implement
the IConnectionPointContainer interface and has to pass back IConnectionPoint
interfaces to clients that want to sink the events that the component sources.
COM+ Events use a different mechanism, in which an event class is declared and
registered on the system, and clients and servers can register themselves with the
COM+ catalog to either publish or subscribe to registered event classes.
In .NET, this is all still true, but once again the RCW will shield you from all the
gory details and allow you to sink COM and COM+ events as if they were .NET
events. The RCW will contain delegates that wrap the each event method that the
COM object sources on its default event interface. The event interface is imported
with a decorated name similar to the name as it had in the COM server. A second
event interface will also be created in the RCW that has the same name as the
COM event interface, but will have _Event appended to its name. This interface
will have methods to add and remove each event on the interface. These methods
will be called implicitly when you hook up an event with the usual .NET syntax,
such as the += and -= operators in C#. As always, the best way to see what is
going on is to use ildasm.exe to peer into the generated assemblies to look at the
generated types.

Page 46 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


So for example, the Windows Media Player control used in the sample code has
an event called EndOfStream that takes a long [in] value and occurs when the end
of a playing media stream is reached. If you wanted to modify the CSMP3Player
to handle this event and switch to a new track when the end of stream is reached,
you would do something like this:

public class CSMP3P layer : System. WinForms.For m


{
public event
AxMedia Player._Medi aPlayerEvent s_EndOfStrea mEventHandle r
endOfSt reamEvent;
private void streamE nded(long re sult);
...
Initial izeComponent () {
...
axMedia Player1.endO fStreamEvent +=
new
AxMedia Player._Medi aPlayerEvent s_EndOfStrea mEventHandle r(this.st rea
mEnded) ;
...
}
// In clean up co de...
axMedia Player1.endO fStreamEvent -=
new AxMedia Player. _MediaPl ayerEvents_E ndOfStreamEv entHandler
(this.st reamEnded);
}

In this code, you use the delegate for the event that is declared in the RCW
assembly to declare a corresponding event within our class (which you can again
discover using ildasm.exe). You then define a class method with the same
signature as the delegate that will be called when the event occurs to perform
whatever event processing you desire. This is the streamEnded() method in the
code snippet. Finally, to notify the COM object that you want to be informed of
the EndOfStream event, you just add an event handler to the event using the +=
operator, just as you would for a .NET event. This will get translated through the
plumbing of the RCW to an Advise call on the IConnectionPoint interface that
represents the event interface. Finally, when you are done handling the events,
you should remove your event handler with the -= operator, which corresponds to
an IConnectionPoint::Unadvise() call, so all the interface pointers get released
correctly.
Wrapping Up
In this section, I have covered the basics of using COM components and controls
in a .NET application. I covered early and late binding, and how you wire up
events. I hopefully gave you some insight into what is going on behind the scenes
in the runtime to make all this work seamlessly for you and make the integration
of COM components as easy as using .NET components. There are a number of
other interesting things the runtime can do for you and ways you can use COM
objects in .NET that are a little different than the way you would use them in

Page 47 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


unmanaged applications. For example, because .NET wraps COM objects in a
class, you can now inherit from that class and extend the functionality of a COM
object in ways that would not be possible just using containment and aggregation
in the COM world. .NET also makes using enumerations of objects easy. There
are a number of attributes that you can use to control the threading model and
what type of apartment your application appears to live in to the COM object. The
bottom line is that there is an awful lot of work that the .NET runtime and tools
are doing for you to make it easy to migrate to .NET and still use your existing
COM object functionality. I encourage you to dig into the .NET SDK
documentation on COM Interoperability to learn more about these and other
interoperability capabilities that are just too complicated to go into here.

Conclusion
In this eMatter book, I have covered the basics of .NET COM interoperability,
both from the perspective of unmanaged clients using .NET components as if they
were COM components or ActiveX controls, and from the perspective of .NET
applications or components using COM components. There is a lot more that
could be said, especially if you want to dive into COM+ services, custom
marshaling, and some of the more advanced topics in COM development. There is
a great deal of information contained in the .NET SDK documentation on .NET
Interoperability to help you dig deeper. Hopefully this book has laid the
groundwork and has given you a head start to letting you reuse existing
functionality in the unmanaged world as you start to move your code into the
wonderful world of .NET.

Links & Resources


! A Programmer’s Introduction to C#, Eric Gunnerson, aPress, 2000
! Professional Visual C++ 5.0: ActiveX/COM Control Programming by
Panos Economopoulis and Sing Li, Wrox Press 1997, ISBN 1861000375
! Events and Delegates, .NET Developer Column by Richard Grimes, VCDJ
May 2001
! Professional ATL COM Programming by Richard Grimes, Wrox Press
1998, ISBN 1861001401
! DOTNET Newsgroup: http://discuss.develop.com/archives/dotnet.html
! MSDN .NET Framework Site:
http://www.msdn.microsoft.com/net/framework/default.asp

Page 48 “ .N E T an d C O M: W or k i ng T ogether ” www.v c dj.c om


About the Author
Brian Noyes is a senior software engineer with Digital Access Corp. (www.digitalaccess.com). He’s an MCSD
with over 10 years of software development, project management, and test and evaluation experience. He has
Bachelors Degree in Aerospace Engineering from the U.S. Naval Academy, a Masters Degree in Aeronautical
Avionics Engineering from Naval Postgraduate School, and is working on a Masters degree in Computer
Science at University of Colorado, Boulder. He is also a technical reviewer and regular contributor to Visual C++
Developer’s Journal. He has a diverse background, having spent 13 years as an F-14 Radar Intercept Officer
and Aerospace Engineering Duty Officer in the U.S. Navy, doing flight test and project management of aircraft
weapons systems and space systems. He continues to serve as a Naval Reserve officer. Now and then he
finds a few free moments to enjoy some mountain biking, kayaking, surfing, and other outdoor activities. You
can reach him at bnoyes@domeworks.com.

This manuscript was brought to you by Fawcette Technical Publications (FTP) Inc. Since 1990, FTP Inc. has
led the way as an information provider in the two fastest-growing, most important markets in
computing—Windows programming and interactive development. Our core product is technical information that
computer professionals need to help them develop applications and deliver services. FTP Inc. is committed to
providing unique value to this important audience of development professionals who use new programming
approaches, such as component-oriented programming and client/server-based Web development, to create
the next generation of custom and enterprise computer applications.

Subscriptions to FTP publications are easy to order. Pick the method most convenient to you:

! Via the web: Go to the web address below and click on the “Click Here to Subscribe Online” button.
! Via phone: Call toll-free: 800-848-5523 (outside the US: 650-833-7100).
! Via fax: 650-321-3818.

You can start a no-risk subscription that you can easily cancel if you are not completely satisfied.
Special Offer for eMatter readers at www.vbpj.com/ematter/
VBPJ Magazine
www.vbpj.com
The only magazine dedicated to Visual Basic programming! VBPJ helps professional
developers program better and faster by providing hands-on, how-to articles about
developing Windows applications with VB and VB tools.
Special Offer for eMatter readers at exchange.devx.com/ematter/
Exchange & Outlook Magazine
exchange.devx.com
Exchange & Outlook magazine delivers exactly what you need to take Exchange and
Outlook technologies to the next level in your corporation. It’s filled with hands-on, in-
depth technical information. Each issue contains proven strategies you can put to work
immediately.
Special Offer for eMatter readers at www.java-pro.com/ematter/
Java Pro Magazine
www.java-pro.com
For hands-on, code-intensive, product-oriented information that will help you harness
Java's unique capabilities, there's only one source. Java Pro delivers serious solutions for
developers using Java in Internet/intranet enterprise computing.
Special Offer for eMatter readers at www.xmlmag.com/ematter/
XML Magazine
www.xmlmag.com
XML Magazine is the ONLY magazine dedicated to helping IT-development professionals
integrate XML into their application planning, and choose the right tools to implement
XML.

You might also like