Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
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.
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.
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.
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
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
[
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);
};
[HasDefaultInterface()]
public class simplecomponent : IDoSimpleThings
{
public simplecomponent() { }
[
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.
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
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.
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.
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.
namespace MP3HeaderReaderLibrary
{
using System;
[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() {}
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
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
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.
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:
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
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:
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.
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
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
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.
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:
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.
class MyContr ol {
...
void Somethi ngHappened()
{
if (doSomet hing != null )
doSomet hing();
}
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
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:
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.
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
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.
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.
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
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;
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:
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:
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
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.
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.