
Pro CSharp 2008 And The .NET 3.5 Platform [eng]
.pdf
1292 APPENDIX A ■ COM AND .NET INTEROPERABILITY
Table A-2. Hidden COM Interfaces
Hidden COM Interface |
Meaning in Life |
IConnectionPointContainer |
Enable a coclass to send events back to an interested client. |
IConnectionPoint |
VB6 automatically provides a default implementation of each of |
|
these interfaces. |
IDispatch |
Facilitate “late binding” to a coclass. Again, when you are |
IProvideClassInfo |
building VB6 COM types, these interfaces are automatically |
|
supported by a given COM type. |
IErrorInfo |
These interfaces enable COM clients and COM objects to send |
ISupportErrorInfo |
and respond to COM errors. |
ICreateErrorInfo |
|
IUnknown |
The granddaddy of COM. Manages the reference count of the |
|
COM object and allows clients to obtain interfaces from the |
|
coclass. |
|
|
The Role of COM IDL
At this point, you hopefully have a solid understanding of the role of the interop assembly and the RCW. Before you go much further into the COM to .NET conversion process, it is necessary to review some of the finer details of COM IDL. Understand, of course, that this appendix is not intended to function as a complete COM IDL tutorial; however, to better understand the interop layer, you only need to be aware of a few IDL constructs.
All .NET assemblies contain metadata. Formally speaking, metadata is used to describe each and every aspect of a .NET assembly, including the internal types (their members, base class, and so on), assembly version, and optional assembly-level information (strong name, culture, and so on).
In many ways, .NET metadata is the big brother of an earlier metadata format used to describe classic COM servers. Classic ActiveX COM servers (*.dlls or *.exes) document their internal types using a type library, which may be realized as a stand-alone *.tlb file or bundled into the COM server as an internal resource (which is the default behavior of VB6). COM type libraries are typically created using a metadata language called the Interface Definition Language and a special compiler named midl.exe (the Microsoft IDL compiler).
VB6 does a fantastic job of hiding type libraries and IDL from view. In fact, many skilled VB COM programmers can live a happy and productive life ignoring the syntax of IDL altogether. Nevertheless, whenever you compile ActiveX project workspace types, VB automatically generates and embeds the type library within the physical *.dll or *.exe COM server. Furthermore, VB6 ensures that the type library is automatically registered under a very particular part of the system registry: HKEY_CLASSES_ROOT\TypeLib (see Figure A-8).

APPENDIX A ■ COM AND .NET INTEROPERABILITY |
1293 |
Figure A-8. HKCR\TypeLib lists all registered type libraries on a given machine.
Type libraries are referenced all the time by numerous IDEs. For example, whenever you access the Project References menu selection of VB6, the IDE consults HKCR\TypeLib to determine each and every registered type library, as shown in Figure A-9.
Figure A-9. Referencing COM type information from VB6
Likewise, when you open the VB6 Object Browser, the VB6 IDE reads the type information and displays the contents of the COM server using a friendly GUI, as shown in Figure A-10.

1294 APPENDIX A ■ COM AND .NET INTEROPERABILITY
Figure A-10. Viewing type libraries using the VB6 Object Browser
Observing the Generated IDL for Your VB COM Server
Although the VB6 Object Browser displays all COM types contained within a type library, the OLE View utility (oleview.exe) allows you to view the underlying IDL syntax used to build the corresponding type library. If you have installed Visual Basic 6.0, you can open OLE View via Start All Programs Microsoft Visual Studio 6.0 Microsoft Visual Studio 6.0 Tools and locate the SimpleComServer server under the Type Libraries node of the tree view control, as shown in Figure A-11.
Figure A-11. Hunting down SimpleComServer using the OLE/COM object viewer
If you were to double-click the type library icon, you would open a new window that shows you all of the IDL tokens that constitute the type library generated by the VB6 compiler. Here is the rele- vant—and slightly reformatted—IDL (your [uuid] values will differ):
[uuid(8AED93CB-7832-4699-A2FC-CAE08693E720), version(1.0)] library SimpleComServer
{
importlib("stdole2.tlb"); interface _ComCalc;
[odl, uuid(5844CD28-2075-4E77-B619-9B65AA0761A3), version(1.0), hidden, dual, nonextensible, oleautomation]
interface _ComCalc : IDispatch { [id(0x60030000)]

APPENDIX A ■ COM AND .NET INTEROPERABILITY |
1295 |
HRESULT Add([in] short x, [in] short y, [out, retval] short* ); [id(0x60030001)]
HRESULT Subtract([in] short x, [in] short y, [out, retval] short* ); };
[uuid(012B1485-6834-47FF-8E53-3090FE85050C), version(1.0)] coclass ComCalc {
[default] interface _ComCalc;
};
};
IDL Attributes
To begin parsing out this IDL, notice that IDL syntax contains blocks of code placed in square brackets ([...]). Within these brackets is a comma-delimited set of IDL keywords, which are used to disambiguate the “very next thing” (the item to the right of the block or the item directly below the block). These blocks are IDL attributes that serve the same purpose as .NET attributes (i.e., they describe something). One key IDL attribute is [uuid], which is used to assign the GUID of a given COM type. As you may already know, just about everything in COM is assigned a GUID (interfaces, COM classes, type libraries, and so on), which is used to uniquely identify a given item.
The IDL Library Statement
Starting at the top, you have the COM library statement, which is marked using the IDL library keyword. Contained within the library statement are each and every interface and COM class, and any enumeration and user-defined type. In the case of SimpleComServer, the type library lists exactly one COM class, ComCalc, which is marked using the coclass (i.e., COM class) keyword.
The Role of the [default] Interface
According to the laws of COM, the only possible way in which a COM client can communicate with a COM class is to use an interface reference (not an object reference). If you have created C++-based COM clients, you are well aware of the process of querying for a given interface, releasing the interface when it is no longer used, and so forth. However, when you make use of VB6 to build COM clients, you receive a default interface on the COM class automatically.
When you build VB6 COM servers, any public member on a *.cls file (such as your Add() function) is placed onto the “default interface” of the COM class. Now, if you examine the class definition of ComCalc, you can see that the name of the default interface is _ComCalc:
[uuid(012B1485-6834-47FF-8E53-3090FE85050C), version(1.0)] coclass ComCalc {
[default] interface _ComCalc;
};
In case you are wondering, the name of the default interface VB6 constructs in the background is always _NameOfTheClass (the underscore is a naming convention used to specify a hidden interface). Thus, if you have a class named Car, the default interface is _Car, a class named DataConnector has a default interface named _DataConnector, and so forth.
Under VB6, the default interface is completely hidden from view. However, when you write the following VB6 code:
' VB 6.0 COM client code.
Dim c As ComCalc
Set c = New ComCalc ' [default] _ComCalc interface returned automatically!

1296 APPENDIX A ■ COM AND .NET INTEROPERABILITY
the VB runtime automatically queries the object for the default interface (as specified by the type library) and returns it to the client. Because VB always returns the default interface on a COM class, you can pretend that you have a true object reference. However, this is only a bit of syntactic sugar provided by VB6. In COM, there is no such thing as a direct object reference. You always have an interface reference (even if it happens to be the default).
The Role of IDispatch
If you examine the IDL description of the default _ComCalc interface, you see that this interface derives from a standard COM interface named IDispatch. While a full discussion concerning the role of IDispatch is well outside of the scope of this appendix, simply understand that this is the interface that makes it possible to interact with COM objects on the Web from within a classic Active Server Page, as well as anywhere else where late binding is required.
IDL Parameter Attributes
The final bit of IDL that you need to be aware of is how VB6 parameters are expressed under the hood. Under VB6 all parameters are passed by reference, unless the ByVal keyword is used explicitly, which is represented using the IDL [in] attribute. Furthermore, a function’s return value is marked using the [out, retval] attributes. Thus, the following VB6 function:
' VB6 function
Public Function Add(ByVal x as Integer, ByVal y as Integer) as Integer Add = x + y
End Function
would be expressed in IDL like so:
HRESULT Add([in] short* x, [in] short* y, [out, retval] short*);
On the other hand, if you do not mark a parameter using the VB6 ByVal keyword, ByRef is assumed:
' These parameters are passed ByRef under VB6!
Public Function Subtract(x As Integer, y As Integer) As Integer Subtract = x - y
End Function
ByRef parameters are marked in IDL via the [in, out] attributes:
HRESULT Subtract([in, out] short x, [in, out] short y, [out, retval] short*);
Using a Type Library to Build an Interop Assembly
To be sure, the VB6 compiler generates many other IDL attributes under the hood, and you will see additional bits and pieces where appropriate. However, at this point, I am sure you are wondering exactly why I spent the last several pages describing COM IDL. The reason is simple: when you add a reference to a COM server using Visual Studio 2008, the IDE reads the type library to build the corresponding interop assembly. While VS 2008 does a very good job of generating an interop assembly, the Add Reference dialog box follows a default set of rules regarding how the interop assembly will be constructed and does not allow you to fine-tune this construction.
If you require a greater level of flexibility, you have the option of generating interop assemblies at the command prompt, using a .NET tool named tlbimp.exe (the type library importer utility). Among other things, tlbimp.exe allows you to control the name of the .NET namespace that will

APPENDIX A ■ COM AND .NET INTEROPERABILITY |
1297 |
contain the types and the name of the output file. Furthermore, if you wish to assign a strong name to your interop assembly in order to deploy it to the GAC, tlbimp.exe provides the /keyfile flag to specify the *.snk file (see Chapter 15 for details regarding strong names). To view all of your options, simply type tlbimp at a Visual Studio 2008 command prompt and hit the Enter key, as shown in Figure A-12.
Figure A-12. Options of tlbimp.exe
While this tool has numerous options, the following command could be used to generate a strongly named interop assembly (assuming you have generated a *.snk file named myKeyPair.snk) named CalcInteropAsm.dll:
tlbimp SimpleComServer.dll /keyfile:myKeyPair.snk /out:CalcInteropAsm.dll
Again, if you are happy with the interop assembly created by Visual Studio 2008, you are not required to directly make use of tlbimp.exe.
Late Binding to the CoCalc Coclass
Once you have generated an interop assembly, your .NET applications are now able to make use of their types using early binding or late binding techniques. Given that you have already seen how to create a COM type using early binding at the opening of this appendix (via the C# new keyword), let’s turn our attention to activating a COM object using late binding.
As you recall from Chapter 16, the System.Reflection namespace provides a way for you to programmatically inspect the types contained in a given assembly at runtime. In COM, the same sort of functionality is supported through the use of a set of standard interfaces (e.g., ITypeLib, ITypeInfo, and so on). When a client binds to a member at runtime (rather than at compile time), the client is said to exercise “late” binding.
By and large, you should always prefer the early binding technique using the C# new keyword. There are times, however, when you must use late binding to a coclass. For example, some legacy COM servers may have been constructed in such a way that they provide no type information whatsoever. If this is the case, it should be clear that you cannot run the tlbimp.exe utility in the first place. For these rare occurrences, you can access classic COM types using .NET reflection services.


APPENDIX A ■ COM AND .NET INTEROPERABILITY |
1299 |
Building a More Elaborate COM Server
So much for Math 101. It’s time to build a VB6 ActiveX server that makes use of more elaborate COM programming techniques. Create a brand-new ActiveX *.dll workspace named Vb6ComCarServer. Rename your initial class to CoCar, which is implemented like so:
Option Explicit
'A COM enum.
Enum CarType Viper Colt
BMW End Enum
'A COM Event.
Public Event BlewUp()
'Member variables.
Private currSp As Integer Private maxSp As Integer Private Make As CarType
'Remember! All Public members
'are exposed by the default interface!
Public Property Get CurrentSpeed() As Integer CurrentSpeed = currSp
End Property
Public Property Get CarMake() As CarType
CarMake = Make
End Property
Public Sub SpeedUp()
currSp = currSp + 10
If currSp >= maxSp Then
RaiseEvent BlewUp ' Fire event if you max out the engine.
End If
End Sub
Private Sub Class_Initialize()
MsgBox "Init COM car"
End Sub
Public Sub Create(ByVal max As Integer, _ ByVal cur As Integer, ByVal t As CarType) maxSp = max
currSp = cur Make = t
End Sub
As you can see, this is a simple COM class that mimics the functionality of the C# Car class used throughout this text. The only point of interest is the Create() subroutine, which allows the caller to pass in the state data representing the Car object.

1300 APPENDIX A ■ COM AND .NET INTEROPERABILITY
Supporting an Additional COM Interface
Now that you have fleshed out the details of building a COM class with a single (default) interface, insert a new *.cls file that defines the following IDriverInfo interface:
Option Explicit
' Driver has a name
Public Property Let DriverName(ByVal s As String) End Property
Public Property Get DriverName() As String End Property
If you have created COM objects supporting multiple interfaces, you are aware that VB6 provides the Implements keyword. Once you specify the interfaces implemented by a given COM class, you are able to make use of the VB6 code window to build the method stubs. Assume you have added a private String variable (driverName) to the CoCar class type and implemented the IDriverInfo interface as follows:
'Implemented interfaces
'[General][Declarations]
Implements IDriverInfo
...
'***** IDriverInfo impl ***** '
Private Property Let IDriverInfo_DriverName(ByVal RHS As String)
driverName = RHS
End Property
Private Property Get IDriverInfo_DriverName() As String
IDriverInfo_driverName = driverName
End Property
To wrap up this interface implementation, set the Instancing property of IDriverInfo to PublicNotCreatable (given that the outside world should not be able to allocate interface types).
Exposing an Inner Object
Under VB6 (as well as COM itself), we do not have the luxury of classical implementation inheritance. Rather, we’re limited to the use of the containment/delegation model (the “has-a” relationship). For testing purposes, add a final *.cls file to your current VB6 project named Engine, and set its instancing property to PublicNotCreatable (as you want to prevent the user from directly creating an Engine object).
The default public interface of Engine is short and sweet. Define a single function that returns an array of strings to the outside world representing pet names for each cylinder of the engine (okay, no right-minded person gives friendly names to his or her cylinders, but hey . . .):
Option Explicit
Public Function GetCylinders() As String() Dim c(3) As String
c(0) = "Grimey"
c(1) = "Thumper"
c(2) = "Oily"
c(3) = "Crusher" GetCylinders = c
End Function

APPENDIX A ■ COM AND .NET INTEROPERABILITY |
1301 |
Finally, add a method to the default interface of CoCar named GetEngine(), which returns an instance of the contained Engine (I assume you will create a Private member variable named eng of type Engine for this purpose):
' Return the Engine to the world.
Public Function GetEngine() As Engine Set GetEngine = eng
End Function
At this point, you have an ActiveX server that contains a COM class supporting two interfaces. As well, you are able to return an internal COM type using the [default] interface of the CoCar and interact with some common programming constructs (enums and COM arrays). Go ahead and compile your sever (setting binary compatibility, once finished), and then close down your current VB6 workspace.
■Source Code The Vb6ComCarServer project is included under the Appendix A subdirectory.
Examining the Interop Assembly
Rather than making use of the tlbimp.exe utility to generate our interop assembly, simply create a new console project (named CSharpCarClient) using Visual Studio 2008 and set a reference to the Vb6ComCarServer.dll using the COM tab of the Add Reference dialog box. Now, examine the interop assembly using the VS 2008 Object Browser utility, as shown in Figure A-13.
Figure A-13. The Interop.VbComCarServer.dll assembly
Once again we have a number of Class-suffixed and underscore-prefixed interface types, as well as a number of new items we have not yet examined, whose names suggest they may be used to handle COM to .NET event notifications (__CoCar_Event, __CoCar_SinkHelper, and __CoCarBlewUpEventHandler in particular). Recall from earlier in this appendix, I mentioned that