PART 8

Appendixes APPENDIX A

COM and .NET Interoperability

The goal of this book was to provide you with a solid foundation in the # language and the core services provided by the .NET platform. I suspect that when you contrast the object model provided by .NET to that of ’s previous component architecture (COM), you’ll no doubt be con- vinced that these are two entirely unique systems. Regardless of the fact that COM is now considered to be a legacy framework, you may have existing COM-based systems that you would like to inte- grate into your new .NET applications. Thankfully, the .NET platform provides various types, tools, and namespaces that make the process of COM and .NET interoperability quite straightforward. This appendix begins by examin- ing the process of .NET to COM interoperability and the related Runtime Callable Wrapper (RCW). The latter part of this appendix examines the opposite situation: a COM type communicating with a .NET type using a COM Callable Wrapper (CCW).

■Note A full examination of the .NET interoperability layer would require a book unto itself. If you require more details than presented in this appendix, check out my book COM and .NET Interoperability (Apress, 2002).

The Scope of .NET Interoperability Recall that when you build assemblies using a .NET-aware compiler, you are creating that can be hosted by the (CLR). Managed code offers a number of ben- efits such as automatic memory management, a unified type system (the CTS), self-describing assemblies, and so forth. As you have also seen, .NET assemblies have a particular internal compo- sition. In addition to CIL instructions and type , assemblies contain a that fully documents any required external assemblies as well as other file-related details (strong naming, version number, etc.). On the other side of the spectrum are legacy COM servers (which are, of course, unmanaged code). These binaries bear no relationship to .NET assemblies beyond a shared file extension (*.dll or *.exe). First of all, COM servers contain platform-specific machine code, not platform-agnostic CIL instructions, and work with a unique set of data types (often termed oleautomation or variant- compliant data types), none of which are directly understood by the CLR. In addition to the necessary COM-centric infrastructure required by all COM binaries (such as registry entries and support for core COM interfaces like IUnknown) is the fact that COM types demand to be reference counted in order to correctly control the lifetime of a COM object. This is in stark contrast, of course, to a .NET object, which is allocated on a managed heap and handled by the CLR garbage collector. Given that .NET types and COM types have so little in common, you may wonder how these two architectures can make use of each others’ services. Unless you are lucky enough to work for a 1283 1284 APPENDIX A ■ COM AND .NET INTEROPERABILITY

company dedicated to “100% Pure .NET” development, you will most likely need to build .NET solutions that use legacy COM types. As well, you may find that a legacy COM server might like to communicate with the types contained within a shiny new .NET . The bottom line is that for some time to come, COM and .NET must learn how to get along. This appendix examines the process of managed and unmanaged types living together in harmony using the .NET interoperability layer. In general, the .NET Framework supports two core flavors of interoperability:

• .NET applications using COM types • COM applications using .NET types

As you’ll see throughout this appendix, the .NET Framework 3.5 SDK and Visual Studio 2008 supply a number of tools that help bridge the gap between these unique architectures. As well, the .NET base class library defines a namespace (System.Runtime.InteropServices) dedicated solely to the issue of interoperability. However, before diving in too far under the hood, let’s look at a very simple example of a .NET class communicating with a COM server.

■Note The .NET platform also makes it very simple for a .NET assembly to call into the underlying API of the (as well as any C-based unmanaged *.dll) using a technology termed platform invocation (or simply PInvoke). From a C# point of view, working with PInvoke involves at absolute minimum applying the [DllImport] attribute to the external method to be executed. Although PInvoke is not examined in this appendix, check out the [DllImport] attribute using the .NET Framework 3.5 SDK documentation for further details.

A Simple Example of .NET to COM Interop To begin our exploration of interoperability services, let’s see just how simple things appear on the surface. The goal of this section is to build a 6.0 ActiveX *.dll server, which is then con- sumed by a C# application.

■Note There are many COM frameworks in existence beyond VB6 (such as the Active Template Library [ATL] and the Microsoft Foundation Classes [MFC]). VB6 has been chosen to build the COM servers in this appendix, as it provides the most user-friendly syntax to build COM applications. Feel free to make use of ATL/MFC if you so choose.

Fire up VB6, and create a new ActiveX *.dll project named SimpleComServer, rename your ini- tial class file to ComCalc.cls, and name the class itself ComCalc. As you may know, the name of your project and the names assigned to the contained classes will be used to define the programmatic identifier (ProgID) of the COM types (SimpleComServer.ComCalc, in this case). Finally, define the fol- lowing methods within ComCalc.cls: ' The VB6 COM object Option Explicit

Public Function Add(ByVal x As Integer, ByVal y As Integer) As Integer Add=x+y End Function APPENDIX A ■ COM AND .NET INTEROPERABILITY 1285

Public Function Subtract(ByVal x As Integer, ByVal y As Integer) As Integer Subtract = x - y End Function At this point, compile your *.dll (via the File ➤ Make menu option) and, just to keep things peaceful in the world of COM, establish binary compatibility (via the Component tab of the pro- ject’s Property page) before you exit the VB6 IDE. This will ensure that if you recompile the appli- cation, VB6 will preserve the assigned globally unique identifiers (GUIDs).

■Source Code The SimpleComServer project is located under the Appendix A subdirectory.

Building the C# Client Now open up Visual Studio 2008 and create a new C# Console Application named CSharpComClient. When you are building a .NET application that needs to communicate with a legacy COM applica- tion, the first step is to reference the COM server within your project (much like you reference a .NET assembly). To do so, simply access the Project ➤ Add Reference menu selection and select the COM tab from the Add Reference dialog box. The name of your COM server will be listed alphabetically, as the VB6 compiler updated the system registry with the necessary listings when you compiled your project. Go ahead and select the SimpleComServer.dll as shown in Figure A-1 and close the dialog box.

Figure A-1. Referencing a COM server using Visual Studio 2008

Now, if you examine the References folder of the Solution Explorer, you see what looks to be a new .NET assembly reference added to your project, as illustrated in Figure A-2. Formally speaking, assemblies that are generated when referencing a COM server are termed interop assemblies. With- out getting too far ahead of ourselves at this point, simply understand that interop assemblies contain .NET descriptions of COM types. 1286 APPENDIX A ■ COM AND .NET INTEROPERABILITY

Figure A-2. The referenced interop assembly

Although we have not added any code to our initial C# class type, if you compile your applica- tion and examine the project’s bin\Debug directory, you will find that a local copy of the generated interop assembly has been placed in the application directory (see Figure A-3). Notice that Visual Studio 2008 automatically prefixes Interop. to interop assemblies generated when using the Add Reference dialog box—however, this is only a convention; the CLR does not demand that interop assemblies follow this particular naming convention.

Figure A-3. The autogenerated interop assembly

To complete this initial example, update the Main() method of your initial class to invoke the Add() method from a ComCalc object and display the result. For example: using System; using SimpleComServer;

namespace CSharpComClient { class Program { static void Main(string[] args) { APPENDIX A ■ COM AND .NET INTEROPERABILITY 1287

Console.WriteLine("***** The .NET COM Client App *****"); ComCalc comObj = new ComCalc(); Console.WriteLine("COM server says 10 + 832 is {0}", comObj.Add(10, 832)); Console.ReadLine(); } } } As you can see from the previous code example, the namespace that contains the ComCalc COM object is named identically to the original VB6 project (notice the using statement). The output shown in Figure A-4 is as you would expect.

Figure A-4. Behold! .NET to COM interoperability

As you can see, consuming a COM type from a .NET application can be a very transparent operation indeed. As you might imagine, however, a number of details are occurring behind the scenes to make this communication possible, the gory details of which you will explore throughout this appendix, beginning with taking a deeper look into the interop assembly itself.

Investigating a .NET Interop Assembly As you have just seen, when you reference a COM server using the Visual Studio 2008 Add Reference dialog box, the IDE responds by generating a brand-new .NET assembly taking an Interop. prefix (such as Interop.SimpleComServer.dll). Just like an assembly that you would create yourself, interop assemblies contain type metadata, an assembly manifest, and under some circumstances may contain CIL code. As well, just like a “normal” assembly, interop assemblies can be deployed privately (e.g., within the directory of the client assembly) or assigned a strong name to be deployed to the GAC. Interop assemblies are little more than containers to hold .NET metadata descriptions of the original COM types. In many cases, interop assemblies do not contain CIL instructions to imple- ment their methods, as the real work is taking place in the COM server itself. The only time an interop assembly contains executable CIL instructions is if the COM server contains COM objects that have the ability to fire events to the client. In this case, the CIL code within the interop assem- bly is used by the CLR to translate the event-handling logic from COM connection points into .NET delegates. At first glance, it may seem that interop assemblies are not entirely useful, given that they do not contain any implementation logic. However, the metadata descriptions within an interop assembly are extremely important, as it will be consumed by the CLR at runtime to build a runtime proxy (termed the Runtime Callable Wrapper, or simply RCW) that forms a bridge between the .NET application and the COM object it is communicating with. You’ll examine the details of the RCW in the next several sections; however, for the time being, open up the Interop.SimpleComServer.dll assembly using ildasm.exe, as you see in Figure A-5. 1288 APPENDIX A ■ COM AND .NET INTEROPERABILITY

Figure A-5. The guts of the Interop.SimpleComServer.dll interop assembly

As you can see, although the original VB6 project only defined a single COM class (ComCalc), the interop assembly contains three types. This can also be verified using the VS 2008 Object Browser (see Figure A-6).

Figure A-6. Hmm, how does a single COM type yield three .NET types?

Simply put, each COM class is represented by three distinct .NET types. First, you have a .NET type that is identically named to the original COM type (ComCalc, in this case). Next, you have a sec- ond .NET type that takes a Class suffix (ComCalcClass). These types are very helpful when you have a COM type that implements several custom interfaces, in that the Class-suffixed types expose all members from each interface supported by the COM type. Thus, from a .NET programmer’s point of view, there is no need to manually obtain a reference to a specific COM interface before invoking its functionality. Although ComCalc did not implement multiple custom interfaces, we are able to invoke the Add() and Subtract() methods from a ComCalcClass object (rather than a ComCalc object) as follows: static void Main(string[] args) { Console.WriteLine("***** The .NET COM Client App *****");

// Now using the Class-suffixed type. ComCalcClass comObj = new ComCalcClass(); Console.WriteLine("COM server says 10 + 832 is {0}", comObj.Add(10, 832)); Console.ReadLine(); } APPENDIX A ■ COM AND .NET INTEROPERABILITY 1289

Finally, interop assemblies define .NET equivalents of any original COM interfaces defined within the COM server. In this case, we find a .NET interface named _ComCalc. Unless you are well versed in the mechanics of VB6 COM, this is certain to appear strange, given that we never directly created an interface in our SimpleComServer project (let alone the oddly named _ComCalc inter- face). The role of these underscore-prefixed interfaces will become clear as you move throughout this appendix; for now, simply know that if you really wanted to, you could make use of interface- based programming techniques to invoke Add() or Subtract(): static void Main(string[] args) { Console.WriteLine("***** The .NET COM Client App *****");

// Now manually obtain the hidden interface. ComCalc itfComInterface = null; ComCalcClass comObj = new ComCalcClass(); itfComInterface = (_ComCalc)comObj;

Console.WriteLine("COM server says 10 + 832 is {0}", itfComInterface.Add(10, 832)); Console.ReadLine(); } Now, do understand that invoking a method using the Class-suffixed or underscore-prefixed interface is seldom necessary. However, as you build more complex .NET applications that need to work with COM types in more sophisticated manners, having knowledge of these types is critical.

■Source Code The CSharpComClient project is located under the Appendix A subdirectory.

Understanding the Runtime Callable Wrapper As mentioned, at runtime the CLR will make use of the metadata contained within a .NET interop assembly to build a proxy type that will manage the process of .NET to COM communication. The proxy to which I am referring is the Runtime Callable Wrapper, which is little more than a bridge to the real COM class (officially termed a coclass). Every coclass accessed by a .NET client requires a corresponding RCW. Thus, if you have a single .NET application that uses three COM coclasses, you end up with three distinct RCWs that map .NET calls into COM requests. Figure A-7 illustrates the big picture.

■Note There is always a single RCW per COM object, regardless of how many interfaces the .NET client has obtained from the COM type (you’ll examine a multi-interfaced VB6 COM object a bit later in this appendix). Using this technique, the RCW can maintain the correct COM identity (and reference count) of the COM object.

Again, the good news is that the RCW is created automatically when required by the CLR. The other bit of good news is that legacy COM servers do not require any modifications to be consumed by a .NET-aware language. The intervening RCW takes care of the internal work. To see how this is achieved, let’s formalize some core responsibilities of the RCW. 1290 APPENDIX A ■ COM AND .NET INTEROPERABILITY

Figure A-7. RCWs sit between the .NET caller and the COM object.

The RCW: Exposing COM Types As .NET Types The RCW is in charge of transforming COM data types into .NET equivalents (and vice versa). As a simple example, assume you have a VB6 COM subroutine defined as follows: ' VB6 COM method definition. Public Sub DisplayThisString(ByVal s as String) The interop assembly defines the method parameter as a .NET System.String: ' C# mapping of COM method. public void DisplayThisString(string s) When this method is invoked by the .NET code base, the RCW automatically takes the incom- ing System.String and transforms it into a VB6 String data type (which, as you may know, is in fact a COM BSTR). As you would guess, all COM data types have a corresponding .NET equivalent. To help you gain your bearings, Table A-1 documents the mapping taking place between COM IDL (interface definition language) data types, the related .NET System data types, and the correspon- ding C# keyword (if applicable).

Table A-1. Mapping Intrinsic COM Types to .NET Types COM IDL Data Type System Types C# Keyword wchar_t, short System.Int16 short long, int System.Int32 int Hyper System.Int64 long unsigned char, byte System.Byte byte single System.Single - double System.Double double VARIANT_BOOL System.Boolean bool APPENDIX A ■ COM AND .NET INTEROPERABILITY 1291

COM IDL Data Type System Types C# Keyword BSTR System.String string VARIANT System.Object object DECIMAL System.Decimal - DATE System.DateTime - GUID System.Guid - CURRENCY System.Decimal - IUnknown System.Object object IDispatch System.Object object

The RCW: Managing a Coclass’s Reference Count Another important duty of the RCW is to manage the reference count of the COM object. As you may know from your experience with COM, the COM reference-counting scheme is a joint venture between coclass and client and revolves around the proper use of AddRef() and Release() calls. COM objects self-destruct when they detect that they have no outstanding references. However, .NET types do not use the COM reference-counting scheme, and therefore a .NET client should not be forced to call Release() on the COM types it uses. To keep each participant happy, the RCW caches all interface references internally and triggers the final release when the type is no longer used by the .NET client. The bottom line is that similar to VB6, .NET clients never explicitly call AddRef(), Release(), or QueryInterface().

■Note If you wish to directly interact with a COM object’s reference count from a .NET application, the System. Runtime.InteropServices namespace provides a type named Marshal. This class defines a number of static methods, many of which can be used to manually interact with a COM object’s lifetime. Although you will typically not need to make use of Marshal in most of your applications, consult the .NET Framework 3.5 SDK documenta- tion for further details.

The RCW: Hiding Low-Level COM Interfaces The final core service provided by the RCW is to consume a number of low-level COM interfaces. Because the RCW tries to do everything it can to fool the .NET client into thinking it is directly com- municating with a native .NET type, the RCW must hide various low-level COM interfaces from view. For example, when you build a COM class that supports IConnectionPointContainer (and maintains a subobject or two supporting IConnectionPoint), the coclass in question is able to fire events back to the COM client. VB6 hides this entire process from view using the Event and RaiseEvent keywords. In the same vein, the RCW also hides such COM “goo” from the .NET client. Table A-2 outlines the role of these hidden COM interfaces consumed by the RCW. 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 typi- cally 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 correspon- ding type library. If you have installed Visual Basic 6.0, you can open OLE View via Start ➤ All Programs ➤ 6.0 ➤ Microsoft Visual Studio 6.0 Tools and locate the Simple- ComServer 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 key- word. 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 inter- face 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() func- tion) 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 inter- face). 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. 1298 APPENDIX A ■ COM AND .NET INTEROPERABILITY

The process of late binding begins with a client obtaining the IDispatch interface from a given coclass. This standard COM interface defines a total of four methods, only two of which you need to concern yourself with at the moment. First, you have GetIDsOfNames(). This method allows a caller to use late binding by obtaining the numerical value (called the dispatch ID, or DISPID) used to identify the method it is attempting to invoke. In COM IDL, a member’s DISPID is assigned using the [id] attribute. If you examine the IDL code generated by VB6 (using the OLE View tool), you will see that the DISPID of the Add() method has been assigned a DISPID such as the following: [id(0x60030000)] HRESULT Add( [in] short x, [in] short y, [out, retval] short* );

This is the value that GetIDsOfNames() returns to the late-bound client. Once the client obtains this value, it makes a call to the next method of interest, Invoke(). This method of IDispatch takes a number of arguments, one of which is the DISPID obtained using GetIDsOfNames(). In addition, the Invoke() method takes an array of COM VARIANT types that represent the parameters passed to the function. In the case of the Add() method, this array contains two shorts (of some value). The final argument of Invoke() is another VARIANT that holds the return value of the method invocation (again, a short). Although a .NET client using late binding does not directly use the IDispatch interface, the same general functionality comes through using the System.Reflection namespace. To illustrate, the following is another C# client that uses late binding to trigger the Add() logic. Notice that this application does not make reference to the assembly in any way and therefore does not require the use of the tlbimp.exe utility. // Be sure to use the System.Reflection namespace. static void Main(string[] args) { Console.WriteLine("***** The Late Bound .NET Client *****");

// First get IDispatch reference from coclass. Type calcObj = Type.GetTypeFromProgID("SimpleCOMServer.ComCalc"); object calcDisp = Activator.CreateInstance(calcObj);

// Make the array of args. object[] addArgs = { 100, 24 };

// Invoke the Add() method and obtain summation. object sum = null; sum = calcObj.InvokeMember("Add", BindingFlags.InvokeMethod, null, calcDisp, addArgs);

// Display result. Console.WriteLine("Late bound adding: 100 + 24 is: {0}", sum); Console.ReadLine(); }

■Source Code The CSharpComLateBinding application is included under the Appendix A subdirectory. 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 1302 APPENDIX A ■ COM AND .NET INTEROPERABILITY

when a COM object exposes COM events, the interop assembly will contain additional CIL code that is used by the CLR to map COM events to .NET events (you’ll see them in action in just a bit).

Building Our C# Client Application Given that the CLR will automatically create the necessary RCW at runtime, our C# application can program directly against the CoCar, CarType, Engine, and IDriveInfo types as if they were all imple- mented using managed code. Here is the complete implementation, with analysis to follow: // Be sure to import the Vb6ComCarServer namespace. class Program { static void Main(string[] args) { Console.WriteLine("***** CoCar Client App *****");

// Create the COM class using early binding. CoCar myCar = new CoCar();

// Handle the BlewUp event. myCar.BlewUp += new __CoCar_BlewUpEventHandler(myCar_BlewUp);

// Call the Create() method. myCar.Create(50, 10, CarType.BMW);

// Set name of driver. IDriverInfo itf = (IDriverInfo)myCar; itf.DriverName = "Fred"; Console.WriteLine("Drive is named: {0}", itf.DriverName);

// Print type of car. Console.WriteLine("Your car is a {0}.", myCar.CarMake); Console.WriteLine();

// Get the Engine and print name of the cylinders. Engine eng = myCar.GetEngine(); Console.WriteLine("Your Cylinders are named:"); string[] names = (string[])eng.GetCylinders(); foreach (string s in names) { Console.WriteLine(s); } Console.WriteLine();

// Speed up car to trigger event. for (int i = 0; i < 5; i++) { myCar.SpeedUp(); } }

// Handler for the BlewUp event. static void myCar_BlewUp() { Console.WriteLine("Your car is toast!"); } } APPENDIX A ■ COM AND .NET INTEROPERABILITY 1303

Notice that when we call GetCylinders(), we are casting the return value into an array of strings. The reason is the fact that COM arrays are (most often) represented by the SAFEARRAY COM type (which is always the case when building COM applications using VB6). The RCW will map SAFEARRAY types into a System.Array object, rather than automatically mapping SAFEARRAYs into an array represented with C# syntax. Thus, by casting the Array object into a string[], we can process the array more naturally.

Interacting with the CoCar Type Recall that when we created the VB6 CoCar, we defined and implemented a custom COM interface named IDriverInfo, in addition to the automatically generated default interface (_CoCar) created by the VB6 compiler. When our Main() method creates an instance of CoCar, we only have direct access to the members of the _CoCar interface, which as you recall will be composed by each public mem- ber of the COM class: // Here, you are really working with the [default] interface. myCar.Create(50, 10, CarType.BMW); Given this fact, in order to invoke the DriverName property of the IDriverInfo interface, we must explicitly cast the CoCar object to an IDriverInfo interface as follows: // Set name of driver. IDriverInfo itf = (IDriverInfo)myCar; itf.DriverName = "Fred"; Console.WriteLine("Drive is named: {0}", itf.DriverName); Recall, however, that when a type library is converted into an interop assembly, it will contain Class-suffixed types that expose every member of every interface. Therefore, if you so choose, you could simplify your programming if you create and make use of a CoCarClass object, rather than a CoCar object. For example, consider the following subroutine, which makes use of members of the default interface of CoCar as well as members of IDriverInfo: static void UseCar() { // -Class suffix types expose all // members from all interfaces. CoCarClass c = new CoCarClass();

// This property is a member of IDriverInfo. c.DriverName = "Mary";

// This method is a member of _CoCar. c.SpeedUp(); } If you are wondering exactly how this single type is exposing members of each implemented interface, check out the list of implemented interfaces and the base class of CoCarClass using the Visual Studio 2008 Object Browser (see Figure A-14). 1304 APPENDIX A ■ COM AND .NET INTEROPERABILITY

Figure A-14. The composition of CoCarClass

As you can see, this type implements the _CoCar and _IDriverInfo interfaces and exposes them as “normal” public members.

Intercepting COM Events In Chapter 11, you learned about the .NET event model. Recall that this architecture is based on delegating the flow of logic from one part of the application to another. The entity in charge of for- warding a request is a type deriving from System.MulticastDelegate, which we create indirectly in C# using the delegate keyword. When the tlbimp.exe utility encounters event definitions in the COM server’s type library, it responds by creating a number of managed types that wrap the low-level COM connection point architecture. Using these types, you can pretend to add a member to a System.MulticastDelegate’s internal list of methods. Under the hood, of course, the proxy is mapping the incoming COM event to their managed equivalents. Table A-3 briefly describes these types.

Table A-3. COM Event Helper Types Generated Type (Based on the _CarEvents [source] Interface) Meaning in Life __CoCar_Event This is a managed interface that defines the add and remove members used to add (or remove) a method to (or from) the System.MulticastDelegate’s linked list. __CoCar_BlewUpEventHandler This is the managed delegate (which derives from System.MulticastDelegate). __CoCar_SinkHelper This generated class implements the outbound interface in a .NET-aware sink object.

As you would hope, you are able to handle the incoming COM events in the same way you handle events based on the .NET delegation architecture: class Program { static void Main(string[] args) { Console.WriteLine("***** CoCar Client App *****"); CoCar myCar = new CoCar(); APPENDIX A ■ COM AND .NET INTEROPERABILITY 1305

// Handle the BlewUp event. myCar.BlewUp += new __CoCar_BlewUpEventHandler(myCar_BlewUp); ... }

// Handler for the BlewUp event. static void myCar_BlewUp() { Console.WriteLine("Your car is toast!"); } } It is also worth pointing out if your C# code base is able to make use of all of the event-centric notations (anonymous methods, method group conversion, lambda expressions, etc.) when inter- cepting events from COM objects.

■Source Code The CSharpCarClient project is included under the Appendix A subdirectory.

That wraps up our investigation of how a .NET application can communicate with a legacy COM application. Now be aware that the techniques you have just learned would work for any COM server at all. This is important to remember, given that many COM servers might never be rewritten as native .NET applications. For example, the object model of Microsoft Outlook is currently exposed as a COM library. Thus, if you needed to build a .NET program that interacted with this product, the interoperability layer is (currently) mandatory.

Understanding COM to .NET Interoperability The next topic of this appendix is to examine the process of a COM application communicating with a .NET type. This “direction” of interop allows legacy COM code bases (such as an existing VB6 project) to make use of functionality contained within newer .NET assemblies. As you might imag- ine, this situation is less likely to occur than .NET to COM interop; however, it is still worth exploring. For a COM application to make use of a .NET type, we somehow need to fool the COM program into believing that the managed .NET type is in fact unmanaged. In essence, you need to allow the COM application to interact with the .NET type using the functionality required by the COM architecture. For example, the COM type should be able to obtain new interfaces through QueryInterface() calls, simulate unmanaged memory management using AddRef() and Release(), make use of the COM connection point protocol, and so on. Beyond fooling the COM client, COM to .NET interoperability also involves fooling the COM runtime. A COM server is activated using the COM runtime rather than the CLR. For this to happen, the COM runtime must look up numerous bits of information in the system registry (ProgIDs, CLSIDs, IIDs, and so forth). The problem, of course, is that .NET assemblies are not registered in the registry in the first place! Given these points, to make your .NET assemblies available to COM clients, you must take the following steps:

1. Register your .NET assembly in the system registry to allow the COM runtime to locate it. 2. Generate a COM type library (*.tlb) file (based on the .NET metadata) to allow the COM client to interact with the public types. 3. Deploy the assembly in the same directory as the COM client or (more typically) install it into the GAC. 1306 APPENDIX A ■ COM AND .NET INTEROPERABILITY

As you will see, these steps can be performed using Visual Studio 2008 or at the command line using various tools that ship with the .NET Framework 3.5 SDK.

The Attributes of System.Runtime.InteropServices In addition to performing these steps, you will typically also need to decorate your C# types with various .NET attributes, all of which are defined in the System.Runtime.InteropServices name- space. These attributes ultimately control how the COM type library is created and therefore control how the COM application is able to interact with your managed types. Table A-4 documents some (but not all) of the attributes you can use to control the generated COM type library.

Table A-4. Select Attributes of System.Runtime.InteropServices .NET Interop Attribute Meaning in Life [ClassInterface] Used to create a default COM interface for a .NET class type. [ComClass] This attribute is similar to [ClassInterface], except it also provides the ability to establish the GUIDs used for the class ID (CLSID) and interface IDs of the COM types within the type library. [DispId] Used to hard-code the DISPID values assigned to a member for purposes of late binding. [Guid] Used to hard-code a GUID value in the COM type library. [In] Exposes a member parameter as an input parameter in COM IDL. [InterfaceType] Used to control how a .NET interface should be exposed to COM (IDispatch-only, dual, or IUnknown-only). [Out] Exposes a member parameter as an output parameter in COM IDL.

Now do be aware that for simple COM to .NET interop scenarios, you are not required to adorn your .NET code with dozens of attributes in order to control how the underlying COM type library is defined. However, when you need to be very specific regarding how your .NET types will be exposed to COM, the more you understand COM IDL attributes the better, given that the attributes defined in System.Runtime.InteropServices are little more than managed definitions of these IDL keywords.

The Role of the CCW Before we walk through the steps of exposing a .NET type to COM, let’s take a look at exactly how COM programs interact with .NET types using a COM Callable Wrapper, or CCW. As you have seen, when a .NET program communicates with a COM type, the CLR creates a Runtime Callable Wrap- per. In a similar vein, when a COM client accesses a .NET type, the CLR makes use of an intervening proxy termed the COM Callable Wrapper to negotiate the COM to .NET conversion (see Figure A-15). Like any COM object, the CCW is a reference-counted entity. This should make sense, given that the COM client is assuming that the CCW is a real COM type and thus must abide by the rules of AddRef() and Release(). When the COM client has issued the final release, the CCW releases its reference to the real .NET type, at which point it is ready to be garbage collected. APPENDIX A ■ COM AND .NET INTEROPERABILITY 1307

Figure A-15. COM types talk to .NET types using a CCW.

The CCW implements a number of COM interfaces automatically to further the illusion that the proxy represents a genuine coclass. In addition to the set of custom interfaces defined by the .NET type (including an entity termed the class interface that you examine in just a moment), the CCW provides support for the standard COM behaviors described in Table A-5.

Table A-5. The CCW Supports Many Core COM Interfaces CCW-Implemented Interface Meaning in Life IConnectionPoint If the .NET type supports any events, they are represented as COM IConnectionPointContainer connection points. IEnumVariant If the .NET type supports the IEnumerable interface, it appears to the COM client as a standard COM enumerator. IErrorInfo These interfaces allow coclasses to send COM error objects. ISupportErrorInfo ITypeInfo These interfaces allow the COM client to pretend to manipulate an IProvideClassInfo assembly’s COM type information. In reality, the COM client is interacting with .NET metadata. IUnknown These core COM interfaces provide support for early and late IDispatch binding to the .NET type. IDispatchEx

The Role of the .NET Class Interface In classic COM, the only way a COM client can communicate with a COM object is to use an inter- face reference. In contrast, .NET types do not need to support any interfaces whatsoever, which is clearly a problem for a COM caller. Given that classic COM clients cannot work with object refer- ences, another responsibility of the CCW is to expose a class interface to represent each member defined by the type’s public sector. As you can see, the CCW is taking the same approach as Visual Basic 6.0! 1308 APPENDIX A ■ COM AND .NET INTEROPERABILITY

Defining a Class Interface To define a class interface for your .NET types, you will need to apply the [ClassInterface] attrib- ute on each public class you wish to expose to COM. Again, doing so will ensure that each public member of the class is exposed to a default autogenerated interface that follows the same exact naming convention as VB6 (_NameOfTheClass). Technically speaking, applying this attribute is optional; however, you will almost always wish to do so. If you do not, the only way the COM caller can communicate with the type is using late binding (which is far less type safe and typically results in slower performance). The [ClassInterface] attribute supports a named property (ClassInterfaceType) that controls exactly how this default interface should appear in the COM type library. Table A-6 defines the pos- sible settings.

Table A-6. Values of the ClassInterfaceType Enumeration ClassInterfaceType Member Name Meaning in Life AutoDispatch Indicates the autogenerated default interface will only support late binding, and is equivalent to not applying the [ClassInterface] attribute at all. AutoDual Indicates that the autogenerated default interface is a “dual interface” and can therefore be interacted with using early binding or late binding. This would be the same behavior taken by VB6 when it defines a default COM interface. None Indicates that no interface will be generated for the class. This can be helpful when you have defined your own strongly typed .NET interfaces that will be exposed to COM, and do not wish to have the “freebie” interface.

In the next example, you specify ClassInterfaceType.AutoDual as the class interface designa- tion. In this way, late-binding clients such as VBScript can access the Add() and Subtract() methods using IDispatch, while early-bound clients (such as VB6 or C++) can use the class interface (named _VbDotNetCalc).

Building Your .NET Types To illustrate a COM type communicating with managed code, assume you have created a simple C# Class Library project named ComCallableDotNetServer, which defines a class named DotNetCalc. This class will define two simple methods named Add() and Subtract(). The imple- mentation logic is trivial; however, notice the use of the [ClassInterface] attribute: // We need this to obtain the necessary // interop attributes. using System.Runtime.InteropServices;

namespace ComCallableDotNetServer { [ClassInterface(ClassInterfaceType.AutoDual)] public class DotNetCalc { public int Add(int x, int y) { return x + y; } APPENDIX A ■ COM AND .NET INTEROPERABILITY 1309

public int Subtract(int x, int y) { return x - y; } } } As mentioned earlier in this appendix, in the world of COM, just about everything is identified using a 128-bit number termed a GUID. These values are recorded into the system registry in order to define an identity of the COM type. Here, we have not specifically defined GUID values for our DotNetCalc class, and therefore the type library exporter tool (tlbexp.exe) will generate GUIDs on the fly. The problem with this approach, of course, is that each time you generate the type library (which we will do shortly), you receive unique GUID values, which can break existing COM clients. To define specific GUID values, you may make use of the guidgen.exe utility, which is accessi- ble from the Tools ➤ Create Guid menu item of Visual Studio 2008. Although this tool provides four GUID formats, the [Guid] attribute demands the GUID value be defined using the Registry Format option, as shown in Figure A-16.

Figure A-16. Obtaining a GUID value

Once you copy this value to your clipboard (via the Copy GUID button), you can then paste it in as an argument to the [Guid] attribute. Be aware that you must remove the curly brackets from the GUID value! This being said, here is our updated DotNetCalc class type (your GUID value will differ): [ClassInterface(ClassInterfaceType.AutoDual)] [Guid("4137CFAB-530B-4667-ADF2-8E2CD63CB462")] public class DotNetCalc { public int Add(int x, int y) { return x + y; }

public int Subtract(int x, int y) { return x - y; } } On a related note, click the Show All Files button on the Solution Explorer and open up the AssemblyInfo.cs file located under the Properties icon. By default, all Visual Studio 2008 project workspaces are provided with an assembly-level [Guid] attribute used to identify the GUID of the type library generated based on the .NET server (if exposed to COM). 1310 APPENDIX A ■ COM AND .NET INTEROPERABILITY

// The following GUID is for the ID of the typelib if this project is exposed to COM [Assembly: Guid("EB268C4F-EB36-464C-8A25-93212C00DC89")]

Defining a Strong Name As a best practice, all .NET assemblies that are exposed to COM should be assigned a strong name and installed into the (the GAC). Technically speaking, this is not required; however, if you do not deploy the assembly to the GAC, you will need to copy this assembly into the same folder as the COM application making use of it. Given that Chapter 15 already walked you through the details of defining a strongly named assembly, simply generate a new *.snk file for signing purposes using the Signing tab of the Proper- ties editor. At this point, you can compile your assembly and install ComCallableDotNetServer.dll into the GAC using gacutil.exe (again, see Chapter 15 for details). gacutil -i ComCallableDotNetServer.dll

Generating the Type Library and Registering the .NET Types At this point, we are ready to generate the necessary COM type library and register our .NET assem- bly into the system registry for use by COM. Do to so, you can take two possible approaches. Your first approach is to use a command-line tool named regasm.exe, which ships with the .NET Frame- work 3.5 SDK. This tool will add several listings to the system registry, and when you specify the /tlb flag, it will also generate the required type library, as shown here: regasm ComCallableDotNetServer.dll /tlb

■Note The .NET Framework 3.5 SDK also provides a tool named tlbexp.exe. Like regasm.exe, this tool will generate type libraries from a .NET assembly; however, it does not add the necessary registry entries. Given this, it is more common to simply use regasm.exe to perform each required step.

While regasm.exe provides the greatest level of flexibility regarding how the COM type library is to be generated, Visual Studio 2008 provides a handy alternative. Using the Properties editor, simply check the Register for COM interop option on the Compile tab, as shown in Figure A-17, and recom- pile your assembly. Once you have run regasm.exe or enabled the Register for COM Interop option, you will find that your bin\Debug folder now contains a COM type library file (taking a *.tlb file extension).

■Source Code ComCallableDotNetServer application is included under the Appendix A subdirectory. APPENDIX A ■ COM AND .NET INTEROPERABILITY 1311

Figure A-17. Registering an assembly for COM interop using Visual Studio 2008

Examining the Exported Type Information Now that you have generated the corresponding COM type library, you can view its contents using the OLE View utility by loading the *.tlb file. If you load ComCallableDotNetServer.tlb (via the File ➤ View Type Library menu option), you will find the COM type descriptions for each of your .NET class types. For example, the DotNetCalc class has been defined to support the default _DotNetClass interface due to the [ClassInterface] attribute, as well as an interface named (surprise, surprise) _Object. As you would guess, this is an unmanaged definition of the functionality defined by System.Object: [uuid(88737214-2E55-4D1B-A354-7A538BD9AB2D), version(1.0), custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "ComCallableDotNetServer.DotNetCalc")] coclass DotNetCalc { [default] interface _DotNetCalc; interface _Object; }; As specified by the [ClassInterface] attribute, the default interface has been configured as a dual interface, and can therefore be accessed using early or late binding: [odl, uuid(AC807681-8C59-39A2-AD49-3072994C1EB1), hidden, dual, nonextensible, oleautomation, custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "ComCallableDotNetServer.DotNetCalc")] interface _DotNetCalc : IDispatch { [id(00000000), propget, custom({54FC8F55-38DE-4703-9C4E-250351302B1C}, "1")] 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 Add([in] long x, [in] long y, 1312 APPENDIX A ■ COM AND .NET INTEROPERABILITY

[out, retval] long* pRetVal); [id(0x60020005)] HRESULT Subtract( [in] long x, [in] long y, [out, retval] long* pRetVal); }; Notice that the _DotNetCalc interface not only describes the Add() and Subtract() methods, but also exposes the members inherited by System.Object. As a rule, when you expose a .NET class type to COM, all public methods defined up the chain of inheritance are also exposed through the autogenerated class interface.

Building a Visual Basic 6.0 Test Client Now that the .NET assembly has been properly configured to interact with the COM runtime, you can build some COM clients. You can create a simple VB6 Standard *.exe project type (named VB6DotNetClient) and set a reference to the new generated type library (see Figure A-18).

Figure A-18. Referencing your .NET server from VB6

As for the GUI front end, keep things really simple. A single Button object will be used to manipulate the DotNetCalc .NET type. Here is the code (notice that you are also invoking ToString(), defined by the _Object interface): Private Sub btnUseDotNetObject_Click() ' Create the .NET object. Dim c As New DotNetCalc MsgBox c.Add(10, 10), , "Adding with .NET"

' Invoke some members of System.Object. MsgBox c.ToString, , "ToString value" End Sub

■Source Code The VB6DotNetClient application is included under the Appendix A subdirectory. APPENDIX A ■ COM AND .NET INTEROPERABILITY 1313

So, at this point you have seen the process of building .NET applications that talk to COM types and COM applications that talk to .NET types. Again, while there are many additional topics regard- ing the role of interop services, you should be in a solid position for further exploration.

Summary .NET is a wonderful thing. Nevertheless, managed and unmanaged code must learn to work together for some time to come. Given this fact, the .NET platform provides various techniques that allow you to blend the best of both worlds. A major section of this appendix focused on the details of .NET types using legacy COM com- ponents. As you have seen, the process begins by generating an assembly proxy for your COM types. The RCW forwards calls to the underlying COM binary and takes care of the details of mapping COM types to their .NET equivalents. The appendix concluded by examining how COM types can call on the services of newer .NET types. As you have seen, this requires that the creatable types in the .NET assembly are registered for use by COM, and that the .NET types are described via a COM type library. APPENDIX B

Platform-Independent .NET Development with

This appendix introduces you to the topic of cross-platform C# and .NET development using an open source implementation of .NET named Mono (in case you are wondering about the name, “Mono” is a Spanish word for monkey, as in “code monkey,” a term often used to describe individu- als who author code for a living). In this appendix, you will come to understand the role of the Common Language Infrastructure (CLI), the overall scope of Mono, and numerous Mono develop- ment tools. Given your work over the course of this text, you will be in a perfect position to dig further into Mono development as you see fit at the conclusion of this appendix.

■Note If you require a detailed treatment of cross-platform .NET development, I recommend picking up a copy of Cross-Platform .NET Development: Using Mono, Portable .NET, and Microsoft .NET by Mark Easton and Jason King (Apress, 2004).

The Platform-Independent Nature of .NET Historically speaking, when programmers made use of a Microsoft development language or programming framework (VB6, MFC, COM, ATL, etc.), they had to resign themselves to building software that (by and large) only executed on the Windows family of operating systems. Many .NET developers, accustomed to previous Microsoft development options, are quite surprised when they learn that .NET is platform-independent. But it’s true. You can compile and execute .NET assemblies on operating systems other than . Using open source .NET implementations such as Mono, Mac OS X, Solaris, AIX, as well as numerous flavors of Unix/ can be happy homes for your .NET binaries. Furthermore, Mono provides an installation package for (surprise, surprise) Microsoft Windows. Thus, it is possible to build and run .NET assemblies on the Windows operating system, without ever installing the Microsoft .NET Framework 3.5 SDK or the Visual Studio 2008 IDE.

■Note Be aware, however, that if you are only interested in building .NET software for the Windows operating system, the Microsoft .NET Framework 3.5 SDK and Visual Studio 2008 provide the best options for doing so.

Even after developers are made aware of .NET code’s cross-platform capabilities, they often assume that the scope of platform-independent .NET development is limited to little more than 1315 1316 APPENDIX B ■ PLATFORM-INDEPENDENT .NET DEVELOPMENT WITH MONO

“Hello World” console applications. In reality, however, you can build production-ready assemblies that make use of ADO.NET, (in addition to alternative GUI toolkits such as GTK# and Cocoa#), ASP.NET, and XML web services using many of the core namespaces and language features you have seen used throughout this text. The way in which .NET’s cross-platform nature is achieved is different from the approach taking by Sun Microsystems with the handling of the programming platform. Unlike Java, Microsoft itself does not provide installers of .NET for Mac, Linux, etc. Rather, Microsoft has released a set of formalized specifications that other entities can use as a road map for building .NET distributions for their platform(s) of choice. Collectively, these specifications are termed the CLI.

The Role of the CLI As briefly mentioned in Chapter 1, when C# and the .NET platform were released to the world at large, Microsoft Corporation submitted two formal specifications to ECMA (European Computer Manufacturers Association). Once approved, these same specifications were submitted to the International Organization for Standardization (ISO) and ratified shortly thereafter. So, why on earth should you care? Again, these two specifications provide a road map for other companies, developers, universities, and other such organizations to build their own custom distributions of the C# programming language and the .NET platform. The two specifications in question are

• ECMA-334, which defines the syntax and semantics of the C# programming language • ECMA-335, which defines numerous details of the .NET platform, collectively termed the Common Language Infrastructure

ECMA-334 tackles the lexical grammar of C# in an extremely rigorous and scientific manner (as you might guess, this level of detail is quite important to those implementing their own C# com- piler). However, ECMA-335 is the meatier of the two specifications, so much so that it has been broken down into six partitions, as listed in Table B-1.

Table B-1. ECMA-335 Specification Partitions ECMA-335 Partition Meaning in Life Partition I: Architecture Describes the overall architecture of the CLI, including the rules of the , the Common Language Specification, and the mechanics of the .NET runtime engine. Partition II: Metadata Describes the details of the .NET metadata format. Partition III: CIL Describes the syntax and semantics of the common intermediate language (CIL) programming language. Partition IV: Libraries Gives a high-level overview of the minimal and complete class libraries that must be supported by a CLI-compatible .NET distribution. Partition V: Binary Formats Provides details of the portable debug interchange format (CILDB). Portable CILDB files provide a standard way to interchange debugging information between CLI producers and consumers. Partition VI: Annexes Represents a collection of “odds and ends” examining topics such as class library design guidelines and the implementation details of a CIL compiler. APPENDIX B ■ PLATFORM-INDEPENDENT .NET DEVELOPMENT WITH MONO 1317

The point of this appendix is not to dive into the details of the ECMA-334 and ECMA-335 speci- fications—nor are you required to know the ins-and-outs of these documents to understand how to build platform-independent .NET assemblies. However, if you are interested, you can download both of these specifications for free from the ECMA website (http://www.ecma-international.org/ publications/standards).

■Note Even if you have no interest in the cross-platform aspects of .NET, I would recommend reading the ECMA-334 and ECMA-335 specifications, as they provide a number of insights regarding the C# language and the .NET platform.

The Mainstream CLI Distributions To date, there are two mainstream implementations of the CLI, beyond Microsoft’s CLR, , and the Microsoft NET Compact Framework (see Table B-2).

Table B-2. Mainstream .NET CLI Distributions CLI Distribution Supporting Website Meaning in Life Mono http://www.mono-project.com Mono is an open source and commercially supported distribution of .NET sponsored by Novell Corporation. Mono is targeted to run on many popular flavors of Unix/Linux, Mac OS X, Solaris, and Windows. Portable .NET http://www.dotgnu.org Portable .NET is distributed under the GNU General Public License. As the name implies, Portable .NET intends to function on as many operation systems and architectures as possible, including many esoteric platforms such as BeOS, Microsoft Xbox, and Sony PlayStation (no, I’m not kidding about those last two!).

Each of the CLI implementations shown in Table B-2 provide a fully function C# compiler, numerous command-line development tools, a global assembly cache (GAC) implementation, sam- ple code, useful documentation, and dozens of assemblies that constitute the base class libraries. Beyond implementing the core libraries defined by Partition IV of ECMA-335, Mono and Portable .NET provide Microsoft-compatible implementations of mscorlib.dll, System.Data.dll, System.Web.dll, System.Drawing.dll, and System.Windows.Forms.dll (among many others). Furthermore, the Mono and Portable .NET distribution also ship with a handful of assemblies specifically targeted at Unix/Linux and Mac OS X operating systems. For example, Cocoa# is a .NET wrapper around the Mac OX GUI toolkit, Cocoa. In this appendix, I will not dig into these OS- specific binaries and instead stay focused on making use of the OS-agonistic programming stacks.

■Note Portable .NET will not be examined in this appendix. However, it is important to know that Mono is not the only platform-independent distribution of the .NET platform available today. I would recommend you take some time to play around with Portable .NET in addition to the Mono platform. 1318 APPENDIX B ■ PLATFORM-INDEPENDENT .NET DEVELOPMENT WITH MONO

The Scope of Mono Given that Mono is an API built on existing ECMA specifications that originated from Microsoft Corporation, you would be correct in assuming that Mono is playing a constant game of catch up as newer versions of Microsoft’s .NET platform are released. At the time of this writing, Mono is com- patible with C# 2.0/.NET 2.0. Therefore, you are able to build ASP.NET websites, Windows Forms applications, database-centric applications using ADO.NET, and (of course) simple console applications. Currently, Mono is not completely compatible with C# 2008 or .NET 3.0/3.5. What that means is your Mono applications are currently unable (again, at the time of this writing) to make use of the following :

• Windows Presentation Foundation (WPF) • Windows Communication Foundation (WCF) • Windows Workflow Foundation (WF) • The LINQ APIs • C# 2008–specific language features

Rest assured that the Novell-based Mono team is already working on incorporating these APIs and C# 2008 programming features into the Mono project. In fact, many C# 2008 language features are already part of the latest build of Mono (1.2.5), including implicit typing, object initialization syntax, and anonymous types.

■Note The C# 2008 support is enabled by passing the -langversion:linq option to the Mono C# compiler.

In addition, the Olive project, which plans to bring WPF, WCF, and WF into the Mono platform, is currently underway. LINQ support, you will be happy to know, is also moving along nicely.

■Note The Mono website maintains a page that describes the overall road map of Mono’s functionality and plans for future releases (http://www.mono-project.com/plans).

The final point of interest regarding the Mono feature set is that much like Microsoft’s .NET Framework 3.5 SDK, the Mono SDK supports a number of .NET programming languages. While this appendix will stay focused on C#, Mono does provide support for a Visual Basic .NET–compatible compiler, as well as support for many other .NET-aware programming languages.

Obtaining and Installing Mono With this basic primer behind us, we can turn our attention to obtaining and installing Mono on your operating system of choice. Navigate to the Mono website (http://www.mono-project.com) and click the Download Now button to navigate to the downloads page. Here you are able to download a variety of installers. I am assuming that you are installing the Windows distribution of Mono (note that installing Mono will not interfere whatsoever with any existing installation of Microsoft .NET or Visual Studio IDEs). Begin by downloading the current stable Mono installation package for Microsoft Windows and saving the setup program to your local hard drive. APPENDIX B ■ PLATFORM-INDEPENDENT .NET DEVELOPMENT WITH MONO 1319

■Note If you are installing Mono on a Linux-based OS, I’d suggest doing so using the Linux Installer for x86 package, which will allow you to install Mono using a friendly setup wizard (rather than forcing you to install Mono from source; be sure to read the supplied installation notes on the Mono website). If you make use of the Mac OS X Mono installer, the installation process will be identical to installing other Mac-based software.

When you run the setup program, you will be given a chance to install a variety of Mono devel- opment tools beyond the expected base class libraries and the C# programming tools. Specifically, the installer will ask you whether you wish to include GTK# (an open source .NET GUI API based on the Linux-centric GTK toolkit) and XSP (a stand-alone web server, similar to Microsoft’s webdev. webserver.exe). In this appendix, I will assume you have opted for a full installation, so be sure you have checked each option in the setup script (see Figure B-1).

Figure B-1. Select all options for your Mono installation.

All of the remaining options of the Mono installer can be left using the suggested default values.

Examining Mono’s Directory Structure By default, Mono installs under C:\Program Files\Mono- (at the time of this writing, the latest and greatest version of Mono is 1.2.5). Beneath that root you will find a number of subdirecto- ries (see Figure B-2). For this appendix, you need only concern yourself with the following subdirectories:

• bin: Contains a majority of the Mono development tools including the C# command-line compilers • lib\mono\gac: The location of Mono’s global assembly cache 1320 APPENDIX B ■ PLATFORM-INDEPENDENT .NET DEVELOPMENT WITH MONO

Figure B-2. The Mono directory structure

Given that you run the vast majority of the Mono development tools from the command line, you will want to make use of the Mono command prompt, which automatically recognizes each of the command-line development tools. The command prompt (which is functionally equivalent to the Visual Studio 2008 command prompt) can be activated by selecting Start ➤ All Programs ➤ Mono For Windows menu option. To test your installation, enter the following command and press the Enter key: mono --version

If all is well, you should see various details regarding the Mono runtime environment (see Figure B-3).

Figure B-3. The Mono runtime environment APPENDIX B ■ PLATFORM-INDEPENDENT .NET DEVELOPMENT WITH MONO 1321

The Mono Development Tools Similar to the Microsoft’s CLR distribution, Mono ships with a number of managed compilers:

• mcs/gmcs: The C# compilers • vbnc: The Mono Visual Basic compiler • booc: The Boo language compiler • /ilasm2: The Mono CIL compilers

While this appendix focuses only on the C# compilers, again recall that the Mono project does ship with a Visual Basic .NET compiler. While this tool is currently under development, the intended goal is to bring the world of human-readable keywords (Inherits, MustOverride, Implements, etc.) to the world of Unix/Linux and Mac OS X (see http://www.mono-project.com/Visual_Basic for more detail). Boo is an object-oriented statically typed programming language for the CLI that sports a Python-based syntax. Check out http://boo.codehaus.org for more details on the Boo program- ming language. Finally, as you might have guessed, ilasm/ilasm2 are the Mono CIL compilers (the second of which supports .NET 2.0 programming constructs).

Working with the C# Compilers The first C# compiler for the Mono project was mcs, and it’s fully compatible with C# 1.1 (in fact, mcs is written in C#). Like the Microsoft C# command-line compiler (csc.exe), mcs supports response files, a /target: flag (to define the assembly type), an /out: flag (to define the name of the compiled assembly), and a /reference: flag (to update the manifest of the current assembly with external dependencies). You can view all the options of mcs using the following command: mcs -?

The “generic mono C# compiler,” or gmcs, as the name implies, is a version of mcs that has sup- port for .NET 2.0–specific C# language features (generics, covariance/contravariance, nullable types, partial types, anonymous methods, etc.) and references the .NET 2.0–based base class libraries. The command-line options of gmcs are identical to mcs, which you can verify with the following command: gmcs -?

Given the presence of two C# compilers, you might naturally assume that only gmcs can be used to build .NET applications that make use of the C# 2.0 language enhancements. In reality, mcs was the first of the two compilers to support 2.0 features, which were perfected and ported to gmcs. The code examples in this appendix can be compiled using either of the two C# compilers, so pick your poison.

Microsoft-Compatible Mono Development Tools In addition to various managed compilers, Mono ships with various development tools that are functionally equivalent to tools found in the Microsoft .NET SDK (some of which are identically named). Table B-3 enumerates the mappings between some of the commonly used Mono/Microsoft .NET utilities. 1322 APPENDIX B ■ PLATFORM-INDEPENDENT .NET DEVELOPMENT WITH MONO

Table B-3. Mono Command-Line Tools and Their Microsoft .NET Counterparts Mono Utility Microsoft .NET Utility Meaning in Life al al.exe Manipulates assembly manifests and builds multifile assemblies (among other things) gacutil gacutil.exe Interacts with the GAC mono when run with ngen.exe Performs a precompilation of an assembly’s the -aot option CIL code wsdl wsdl.exe Generates client-side proxy code for XML web services disco disco.exe Discovers the URLs of XML web services located on a web server xsd xsd.exe Generates type definitions from an XSD schema file sn sn.exe Generates key data for a strongly named assembly monodis ildasm.exe The CIL disassembler ilasm ilasm.exe The CIL assembler xsp2 webdev.webserver.exe A testing and development ASP.NET web server

Mono-Specific Development Tools In addition, there are Mono development tools for which no direct Microsoft .NET Framework 3.5 SDK equivalents exist, and these are listed in Table B-4.

Table B-4. Mono Tools That Have No Direct Microsoft .NET SDK Equivalent Mono-Specific Development Tool Meaning in Life monop/monop2 The monop (mono print) utility will display the definition of a specified type in the syntax of C#. SQL# The Mono Project ships with a graphical front end (SQL#)to allow you to interact with relational databases using a variety of ADO.NET data providers. Glade 3 This tool is a visual development IDE for building GTK# graphical applications.

■Note You can load SQL# and Glade by using Windows’s Start button and navigating to the Applications folder within the Mono installation directory. Be sure to do so, as it will clearly illustrate how rich the Mono platform has become.

Using monop(2) The monop and monop2 utilities (both of which are shorthand for mono print) can be used to display the C# definition of a given type within a specified assembly. The distinction between these two APPENDIX B ■ PLATFORM-INDEPENDENT .NET DEVELOPMENT WITH MONO 1323

utilities is that monop is programmed to only display types compatible with Microsoft .NET 1.1, whereas monop2 will also display types compatible with Microsoft .NET 2.0. As you might suspect, these tools can be quite helpful when you wish to quickly view a method signature, rather than dig- ging through the formal documentation. By way of a quick test, enter the following command within a Mono command prompt: monop2 System.Object

Figure B-4 shows the definition of our good friend System.Object.

Figure B-4. monop(2) displays C# code definitions for compiled types.

You’ll see the use of additional Mono tools over the course of this appendix; however, you may wish to specify -? as an argument to a tool of interest to see the available command set.

Building .NET Applications with Mono To illustrate Mono in action, you will begin by building a code library named CoreLibDumper.dll. This assembly contains a single class type named CoreLibDumper that supports a static method named DumpTypeToFile(). The method takes a string parameter that represents the fully qualified name of any type within mscorlib.dll and obtains the related type information via the reflection API (see Chapter 16), dumping the class member signatures to a local file on the hard drive.

Building a Mono Code Library Create a new folder on your C drive named MonoCode. Within this new folder, create a subfolder named CoreLibDumper that contains the following C# file (named CorLibDumper.cs): // CoreLibDumper.cs using System; using System.Reflection; using System.IO;

// Define assembly version. [assembly:AssemblyVersion("1.0.0.0")] 1324 APPENDIX B ■ PLATFORM-INDEPENDENT .NET DEVELOPMENT WITH MONO

namespace CoreLibDumper { public class TypeDumper { public static bool DumpTypeToFile(string typeToDisplay) { // Attempt to load type into memory. Type theType = null; try { // Throw exception if we can't find it. theType = Type.GetType(typeToDisplay, true); } catch { return false; }

// Create local *.txt file. using(StreamWriter sw = File.CreateText(string.Format("{0}.txt", theType.FullName))) { // Now dump type to file. sw.WriteLine("Type Name: {0}", theType.FullName); sw.WriteLine("Members:"); foreach(MemberInfo mi in theType.GetMembers()) sw.WriteLine("\t-> {0}", mi.ToString());

} return true; } } } Like the Microsoft C# compiler, the Mono C# compilers support the use of response files (see Chapter 2). While you could compile this file by specifying each required argument manually at the command line, instead create a new file named LibraryBuild.rsp (in the same location as CoreLibDumper.cs) that contains the following command set: /target:library /out:CoreLibDumper.dll CoreLibDumper.cs You can now compile your library at the command line as follows: gmcs @LibraryBuild.rsp

This approach is functionally equivalent to the following (more verbose) command set: gmcs /target:library /out:CoreLibDumper.dll CoreLibDumper.cs

Assigning CoreLibDumper.dll a Strong Name Mono supports the notion of deploying strongly named and shared assemblies (see Chapter 15) to the Mono GAC. To generate the necessary public/private key data, Mono provides the sn command- line utility, which functions more or less identically to Microsoft’s tool of the same name. For example, the following command generates a new *.snk file (specify the -? option to view all possible commands): sn -k myTestKeyPair.snk APPENDIX B ■ PLATFORM-INDEPENDENT .NET DEVELOPMENT WITH MONO 1325

To inform the C# compiler to make use of this key data to assign a strong name to CoreLibDumper.dll, simply update your LibraryBuild.rsp file with the following additional command: /target:library /out:CoreLibDumper.dll /keyfile:myTestKeyPair.snk CoreLibDumper.cs Now recompile your assembly: gmcs @LibraryBuild.rsp

Viewing the Updated Manifest with monodis Before deploying the assembly to the Mono GAC, allow me to introduce the monodis command-line tool, which is the functional equivalent of Microsoft’s ildasm.exe (without the GUI front end). Using monodis, you can view the CIL code, manifest, and type metadata for a specified assembly. In this case, we’re interested in viewing the core details of our (now strongly named) assembly via the --assembly flag. Figure B-5 shows the result of the following command set: monodis --assembly CoreLibDumper.dll

Figure B-5. monodis allows you to view the CIL code, metadata, and manifest of an assembly.

As you can see, the assembly’s manifest now exposes the public key value defined within myTestKeyPair.snk.

Installing Assemblies into the Mono GAC Now that you have provided CoreLibDumper.dll with a strong name, you install it into the Mono GAC using gacutil. Like Microsoft’s tool of the same name, Mono’s gacutil supports options to install, uninstall, and list the current assemblies installed under C:\Program Files\Mono-\ lib\mono\gac. The following command deploys CoreLibDumper.dll to the GAC and sets it up as a shared assembly on the machine. gacutil -i CoreLibDumper.dll 1326 APPENDIX B ■ PLATFORM-INDEPENDENT .NET DEVELOPMENT WITH MONO

■Note Be sure to use a Mono command prompt to install this binary to the Mono GAC! If you use the Microsoft gacutil.exe program, you’ll install CoreLibDumper.dll into the Microsoft GAC!

After running the command, if you open the \gac directory, you should find a new folder named CoreLibDumper (see Figure B-6), which defines a subdirectory that follows the same naming conventions as Microsoft’s GAC (versionOfAssembly__publicKeyToken).

Figure B-6. Deploying our code library to the Mono GAC

■Note Supplying the -l option to gacutil will list out each assembly in the Mono GAC.

Building a Console Application in Mono Your first Mono client will be a simple console-based application named ConsoleClientApp.exe. Create a new file in your C:\MonoCode\CorLibDumper folder, ConsoleClientApp.cs, that contains the following Program type: // This client app makes use of the CoreLibDumper.dll // to dump types to a file. using System; using CoreLibDumper;

namespace ConsoleClientApp { public class Program { public static void Main() { Console.WriteLine( "***** The Type Dumper App *****\n"); APPENDIX B ■ PLATFORM-INDEPENDENT .NET DEVELOPMENT WITH MONO 1327

// Ask user for name of type. string typeName = ""; Console.Write("Please enter type name: "); typeName = Console.ReadLine();

// Now send it to the helper library. if(TypeDumper.DumpTypeToFile(typeName)) Console.WriteLine("Data saved into {0}.txt", typeName); else Console.WriteLine("Error! Can't find that type..."); } } } Notice that the Main() method simply prompts the user for a fully qualified type name. The TypeDumper.DumpTypeToFile() method uses the user-entered name to dump the type’s members to a local file. Next, create a ClientBuild.rsp file for this client application that references CoreLibDumper.dll: /target:exe /out:ConsoleClientApp.exe /reference:CoreLibDumper.dll ConsoleClientApp.cs Now, using a Mono command prompt, change to the folder containing your client files and compile the executable using gmcs as shown here: gmcs @ClientBuild.rsp

Loading Our Client Application in the Mono Runtime At this point, you can load ConsoleClientApp.exe into the Mono runtime engine by specifying the name of the executable (with the *.exe file extension) as an argument to mono: mono ConsoleClientApp.exe

As a test, enter System.Threading.Thread at the prompt, and press the Enter key. You will now find a new file named System.Threading.Thread.txt containing the type’s metadata definition (see Figure B-7). Before moving on to a Windows Forms–based client, try the following experiment. Using the Windows Explorer, rename the CoreLibDumper.dll assembly from the folder containing the client application to DontUseCoreLibDumper.dll. You should be able to still successfully run the client application, as the only reason we needed access to this assembly when building the client was to update the client manifest. At runtime, the Mono runtime will load the version of CoreLibDumper.dll you deployed to the Mono GAC. However, if you open Windows Explorer and attempt to run your client application by double- clicking ConsoleClientApp.exe, you might be surprised to find a FileNotFoundException is thrown. At first glance, you might assume this is due to the fact that you renamed CoreLibDumper.dll from the location of the client application. However, the true reason is because you just loaded ConsoleClientApp.exe into the Microsoft CLR! To run an application under Mono, you must pass it into the Mono runtime via mono. If you do not, you will be loading your assembly into the Microsoft CLR, which assumes all shared assemblies are installed into the Microsoft GAC located in the <%windir%>\Assembly directory. 1328 APPENDIX B ■ PLATFORM-INDEPENDENT .NET DEVELOPMENT WITH MONO

Figure B-7. Result of running our client application

Building a Windows Forms Client Program Before continuing, be sure to rename DontUseCoreLibDumper.dll back to CoreLibDumper.dll. Next, create a new C# file named WinFormsClientApp.cs saved in the same location as your current proj- ect files. This file defines two types, both of which make use of a few C# 2.0 language features, including static classes and anonymous methods: using System; using System.Windows.Forms; using CoreLibDumper; using System.Drawing;

namespace WinFormsClientApp { // Application object. public static class Program { public static void Main() { Application.Run(new MainWindow()); } }

// Our simple Window. public class MainWindow : Form { private Button btnDumpToFile = new Button(); private TextBox txtTypeName = new TextBox(); APPENDIX B ■ PLATFORM-INDEPENDENT .NET DEVELOPMENT WITH MONO 1329

public MainWindow() { // Config the UI. ConfigControls(); }

private void ConfigControls() { // Configure the Form. Text = "My Mono Win Forms App!"; ClientSize = new System.Drawing.Size(366, 90); StartPosition = FormStartPosition.CenterScreen; AcceptButton = btnDumpToFile;

// Configure the Button. btnDumpToFile.Text = "Dump"; btnDumpToFile.Location = new System.Drawing.Point(13, 40);

// Handle click event anonymously. btnDumpToFile.Click += delegate { if(TypeDumper.DumpTypeToFile(txtTypeName.Text)) MessageBox.Show(string.Format( "Data saved into {0}.txt", txtTypeName.Text)); else MessageBox.Show("Error! Can't find that type..."); }; Controls.Add(btnDumpToFile);

// Configure the TextBox. txtTypeName.Location = new System.Drawing.Point(13, 13); txtTypeName.Size = new System.Drawing.Size(341, 20); Controls.Add(txtTypeName); } } } To compile this Windows Forms application using a response file, create a file named WinFormsClientApp.rsp (the contents of which follow) and supply that as an argument to gmcs as shown previously. /target:winexe /out:WinFormsClientApp.exe /r:CoreLibDumper.dll /r:System.Windows.Forms.dll /r:System.Drawing.dll WinFormsClientApp.cs Finally, run your Windows Forms application via mono: mono WinFormsClientApp.exe

Figure B-8 shows the output. 1330 APPENDIX B ■ PLATFORM-INDEPENDENT .NET DEVELOPMENT WITH MONO

Figure B-8. A Windows Forms application build using Mono

Executing Our Windows Forms Application Under Linux Up until now, this appendix has created a few assemblies that could have been compiled using the Microsoft .NET Framework 3.5 SDK. However, the importance of Mono becomes quite clear when you view Figure B-9, which shows the same exact Windows Forms application running under SuSe Linux. Notice how our Windows Forms application has taken on the correct look and feel of my current theme.

Figure B-9. Our Windows Forms application executing under SuSe Linux!

■Source Code The CorLibDumper project can be found under the Appendix B subdirectory.

So, you can compile and execute the same exact C# code shown during this appendix on Linux (or any OS supported by Mono) using the same Mono development tools. In fact, you can deploy or recompile any of the assemblies created in this text that do not use 3.0 or 3.5 programming con- structs to a new Mono-aware OS and run them directly using the mono runtime utility. Because all assemblies simply contain platform-agonistic CIL code, you are not required to recompile the applications whatsoever.

■Note Do recall that Mono 1.2.5 does have limited support for C# 2008 language features, so some of the examples from Chapter 13 should work as-is.

Suggestions for Further Study If you followed along with the materials presented in this book, you already know a great deal about Mono, given that it is an ECMA-compatible implementation of the CLI. If you are interested in learning more about Mono particulars, the first place to begin is the official Mono website (http:// www.mono-project.com). Specifically, you should be sure to examine http://www.mono-project.com/ Use, as this page is an entry point to a number of important topics including database access using ADO.NET, web development using ASP.NET, and so forth. APPENDIX B ■ PLATFORM-INDEPENDENT .NET DEVELOPMENT WITH MONO 1331

As well, I have authored some Mono-centric articles on the DevX website (http://www.devx. com) that may be of interest to you:

• “Mono IDEs: Going Beyond the Command Line”: Examines numerous Mono-aware IDEs • “Building Robust UIs in Mono with Gtk#”: Examines building desktop applications using the GTK# toolkit as an alternative to Windows Forms

Last but not least, be aware of the Mono documentation website (http://www.go-mono.com/ docs). Here you will find documentation on the Mono base class libraries, development tools, and other topics (see Figure B-10).

Figure B-10. The online Mono documentation

■Note Mono’s online documentation website is community supported; therefore, don’t be too surprised if you find some incomplete documentation links! Given that Mono is an ECMA-compatible distribution of Microsoft .NET, you may prefer to make use of the feature-rich MSDN online documentation when exploring Mono.

Summary The point of this appendix was to provide an introduction to the cross-platform nature of the C# programming language and the .NET platform using the Mono framework. As you have seen, Mono ships with a number of command-line tools that allow you to build any variety of .NET assembly, including strongly named assemblies deployed to the GAC, Windows Forms applications, and .NET code libraries. As explained, Mono is not fully compatible with the .NET 3.0 or .NET 3.5 programming APIs (WPF, WCF, WF, or LINQ) or the C# 2008 language features (however, Mono 1.2.5 has some limited 1332 APPENDIX B ■ PLATFORM-INDEPENDENT .NET DEVELOPMENT WITH MONO

C# 2008 language support). Efforts are underway (via the Olive project) to bring these aspects of the Microsoft .NET platform to Mono. In any case, if you need to build .NET applications that can exe- cute under a variety of operating systems, the Mono project is a wonderful choice to do so. Index

Special Characters Add Service Reference option, 872, 897, 908 <%@ XXX %> markers, 1183 Add Web Reference button, 908 <%@Master%> directive, 1217 AddAfterThis( )/AddBeforeThis( ) method, 864 <%@Page%> directive attribute, ASP.NET, 1184 AddCacheDependency( ) method, 1199 <%= .%> notation, 1176 AddFirst( ) method, 864 <%Import%> directive, ASP.NET, 1184–1185 adding and removing controls, ASP.NET, 1212 & operator, pointer types, 407 AddNewCarDialog property, 1114 && operator, 103 AddParams object, 615 \" character, 89 AddRef( ) method, 1291, 1305–1306 \\ character, 89 address attribute, 891 \' character, 89 address element, 889 ? argument, 1323 AddServiceEndpoint( ) method, 892, 905 ? operator, 107 ADO.NET ?? operator, 107 additional namespaces, 736 || operator, 103 vs. ADO classic, 731 + operator, 392 application configuration files, 742–743 != operator, 102 asynchronous data access, 775, 777 < operator, 102 Command object, 759–760 <= operator, 102 connected layer, 755–756 == operator, 102 connected vs. disconnected layer, 733 > operator, 102 connection objects, 756–758 >= operator, 102 ConnectionStringBuilder object, 758–759 * operator, pointer types, 407 connectionStrings element, application configuration, 754–755 Numbers data provider definition, 733 3D rendered animation, 1000 data providers overview, 733, 735 100% code approach, 918 DbDataReader object, 760–761 definition, 731 A deleting records, 765 \a character, 89 example, data provider factory, 751, 753–754 Abort( ) method, Thread type, 595 example database, 744 abstract classes, 203–204, 208, 270 Firebird Interbase data provider, 736 abstract members, 162, 204, 269, 272 IBM DB2 Universal Database data providers, abstract methods, 204–208 736 AccelerationRatio property, 1138 IDbCommand interface, 738–739 AcceptButton property, 971, 981 IDbConnection interface, 737 AcceptChanges( ) method, 785 IDbDataAdapter, IDataAdapter interface, 739 AcceptReturn property, 1081 IDbDataParameter, IDataParameter interface, access modifiers, 163–164 739 accessor method, 164 IDbDataReader, IDataReader interface, 740 action attribute, 1169, 1174, 1195 IDbTransaction interface, 738 Action property, 887 inserting records, 764 Activate( ) method, 972 Microsoft data providers, 735 Activated event, 972 modifying tables, Command object, 763, 771 Active Template Library (ATL), description, 5 multiple result sets, DbDataReader object, 762 ActiveX *.dll project, 1284 MySQL data providers, 736 element, 1221 overview, 731 Add( ) method, 95, 109, 113, 432, 864, 1077, 1259, parameterized command objects, 766–767 1286, 1288, 1298 PostgreSQL providers, 736 provider factory model, 749–751 Add Reference dialog box, 489, 492, 715, 1301 1333 1334 ■INDEX

specifying DbParameter parameters, 767–768 overview, 35 stored procedures using DbCommand, 768 SharpDevelop, 35 System.Data, 737 TextPad development editor, 42 third-party data providers, 736 using Notepad, 46–47 updating records, 765 using SharpDevelop, 48–50 using interfaces, 741–742 Visual C# 2005 Express, 35 AdRotator control, 1218, 1221 Application directory, 689 AdRotator example, ASP.NET, 1221–1222 , garbage collection, 256 AdRotator widget, 1221 application level state data, ASP.NET, 1255 AdvertisementFile property, 1221 application object, 69–70 aggregation, 196 Application property, 1195, 1253, 1256 agnostic manner, 870 application root categories, 249–250 al utility, 1322 application shutdown, ASP.NET, 1258 alert( ) method, 1173 Application type, 965 AllKeys member, HttpApplicationState type, 1255 application data and processing command-line allocating objects with New keyword, 143–144 arguments, 1015–1016 allowAnonymous attribute, Profile Data, 1274 Windows collection, 1016 AllowDBNull property, 787 Application_End( ) event handler, 1252 Alt property, 977 Application_End( ) method, HttpApplication- Angle property, 1136, 1144 derived type, 1258 AngleX property, 1136 Application_Error( ) event handler, 1253 AngleY property, 1136 Application_Start( ) event handler, 1252, 1256, AnimatedButtonWithDiscreteKeyFrames.xaml file, 1259 1143 ApplicationCommands object, 1100 AnimationInXaml.xaml file, 1142 ApplicationCommands.Help option, 1101 AnimationUsingKeyFrames suffix, 1142 Application.Current property, 1014 Annexes, 1316 Application.Current.Shutdown( ) method, 1095 anonymous methods, 370–371 element, 1024 anonymous profiles, 1277 Application-derived class, 1013 anonymous types, 441, 443–444 Application.Exit( ) method, 960, 979, 991 App_Browsers, ASP.NET 2.0 subdirectories, 1190 Application.LoadComponent( ) method, App_Browsers subfolder, ASP.NET 2.0, 1190 1026–1027 App_Code subfolder, ASP.NET 2.0, 1190–1191 ApplicationPath member, HttpRequest Type, 1196 App_Data subfolder, ASP.NET 2.0, 1190 Application.Run( ) method, 957, 965, 972 App_GlobalResources subfolder, ASP.NET 2.0, 1190 applications vs. sessions, ASP.NET, 1254 App_LocalResources subfolder, ASP.NET 2.0, 1190 appSetting element, Web.config, ASP.NET, 1204 App_Themes subfolder, ASP.NET 2.0, 1190 element, 1204, 1206 App_WebReferences subfolder, ASP.NET 2.0, 1190 AppStartUp( ) method, 1012 App.config file, 822, 834, 854, 877, 897, 901, 1189 array manipulation, multidimensional arrays, 119 AppDomains Array object, 471 advantages, 571–572 Array type, 460, 471 creation example code, 574 ArrayList class, 305 loading example code, 575 ArrayList System.Collections class type, 312–313 manipulation example code, 573–574 ArrayOfObjects( ) method, 116 overview, 561 arrays, using interface types in, 281 relationship to processes, 571–572 as keyword, 211, 277 unloading example code, 576–577 ascending operator, 463, 469 AppendText( ) method, FileInfo class, System.IO, AsEnumerable( ) method, 840–841 669, 672 AskForBonus( ) method, 122 AppExit( ) method, 1012 *.asmx file, 908 application cache, 1245, 1259 tag, 1181 Application class, 1006 tag, 1186 application configuration files, ADO.NET, 742–743 scope, 1222–1223 application development tag, 1217 cordbg.exe debugger, 35 tag, 1217, 1222 csc.exe compiler, 35–37 ASP.NET installing .NET 3.5 Framework SDK, 35 <%@Page%> directive, 1184 notepad.exe development editor, 35 <%Import%> directive, 1184–1185 ■INDEX 1335

adding and removing controls, 1212 positioning controls using HTML tables, 1243 AdRotator example, 1221–1222 redirecting users, 1200 AutoEventWireUp attribute, 1201–1202 referencing assemblies, 1190–1191 AutoPostBack property, 1208–1209 referencing AutoLotDAL.dll assembly, 1187 browser statistics in HTTP Request processing, request/response cycle, HTTP, 1163 1196 round-trips (postbacks), 1172 categories of web controls, 1215 script block, 1185 classic ASP, 1175, 1177 server-side event handling, 1208 client-side scripting, 1172–1174 simple web controls, 1213 code-behind, description, 1179 simple website example, 1222 code-behind page model, 1186–1188 single file code model, 1179 compilation cycle, 1192–1193 sorting and paging example, 1227 data-centric single-file test page stateless, description, 1164 adding data access logic, 1182–1183 submitting form data, 1174–1175 designing the UI, 1181–1182 System.Web.UI.Control, 1209–1210, 1212 manually referencing AutoLotDAL.dll, 1180 System.Web.UI.Page, 1194 overview, 1180 System.Web.UI.WebControls namespace, role of ASP.NET directives, 1183–1185 1207–1209 debugging and tracing, 1188 System.Web.UI.WebControls.WebControl, 1213 default.aspx content page example, 1222–1224 themes at faster it Find detailed content page example, 1228–1230 * .skin files, 1238–1239 Document Object Model (DOM), 1172–1173 applying at page level, 1240 Emitting HTML, 1199 applying sitewide, 1239–1240 enumerating controls with Panel control, 1210 assigning programmatically, 1241–1242 Error event, 1202 overview, 1237 feature-rich website example SkinID Property, 1240 http://superindex.apress.com/ defining default.aspx content page, updating code file, 1188 1222–1224 user interface in HTML, 1170–1171 designing inventory content page, 1224–1228 using web controls, 1207–1209 detailed content page, 1228–1230 validating form data, 1174 overview, 1215 validation controls, 1231–1235 working with master pages, 1216–1222 web application, description, 1164 form control declarations, 1185 web development server, 1166–1167 GET and POST, 1174–1175 web page code model, 1179, 1181 HTML document structure, 1168 web server, description, 1164 HTML form development, 1169 WebControl base class properties, 1213 HTML overview, 1167 website directory structure, 1190 HTML web controls, 1214 Windows XP Home Edition, 1166–1167 HTTP overview, 1163–1164 ASP.NET 2.0 HTTP Request members, 1196 namespaces, 1179 HTTP Request processing, 1195–1198 subdirectories, 1190–1191 HTTP Response members, 1198 ASP.NET profile API HTTP Response processing, 1198–1200 accessing profile data programmatically, 1274, IIS virtual directories, 1165 1276–1277 incoming form data, 1197–1198 ASPNETDB database, 1272–1273 inheritance chain, page type, 1194 defining user profile within web.config, in-place editing example, 1227–1228 1273–1274 Internet Information Server (IIS), description, grouping profile data and persisting custom 1164 objects, 1277–1279 inventory content page example, 1224–1228 overview, 1272 IsPostBack property in HTTP Request ASP.NET website administration utility, 1205–1206 processing, 1198 aspnet_regsql.exe command-line utility, 1272 life cycle of a web page, 1200–1203 aspnet_state.exe process, 1270 major categories of web controls, 1213 aspnet_wp.exe process, 1270 master pages example, 1216–1222 tag, 1207 .NET 3.5 web enhancements, 1178 *.aspx file, 1179, 1183, 1194, 1207, 1245, 1248, 1250 overview, 1163 aspx suffix, 1192 page type inheritance chain, 1194 1336 ■INDEX

assemblies Assembly class, System.Reflection, 528, 536–539 Add Reference dialog box, 489, 492 assembly directive, common intermediate app.config file, 502 language (CIL), 630 binary code reuse, 475, 481 AssemblyBuilder, System.Reflection.Emit CIL code, 484, 491 namespace, 649 client application example, 492 AssemblyCompanyAttribute attribute, 550 CLR file header, 483–484 AssemblyCopyrightAttribute attribute, 550 code base config file element, 519–520 AssemblyCultureAttribute attribute, 550 code library, 481 AssemblyDescriptionAttribute attribute, 550 compared with legacy executables, 480 AssemblyInfo.cs file, 549, 1309 consuming shared assemblies, 510–511 AssemblyKeyFileAttribute attribute, 550 cross-language inheritance, 495 AssemblyLoad event, System.AppDomain, 573 definition, 480 Assembly.Load( ) method, 1191 dependentAssembly config file element, Assembly.LoadFrom( ) method, 537, 687, 690 514–515 AssemblyName class, System.Reflection , 485 namespace, 528 dynamic redirection to a specific version, assembly/namespace/type distinction, 23–24 514–515 AssemblyOperatingSystemAttribute attribute, 550 embedded resources, 484 AssemblyProcessorAttribute attribute, 550 example of version updating, 512–515 AssemblyProductAttribute attribute, 550 explicit load request, 499–500 AssemblyRef, 526 global assembly cache (GAC), 28, 482 AssemblyResolve event, System.AppDomain, 573 ildasm exploration of manifest, 490 AssemblyTrademarkAttribute attribute, 550 implicit load request, 499–500 AssemblyVersionAttribute attribute, 550 internal format, 482 [Association] attribute, 851 language integration, 494–495, 498 AsyncCallback delegate, 909 manifest, 12, 481, 484 AsyncCallback delegate, multithreaded metadata description, 12 applications, 591–592 module-level manifest, 497 asynchronous data access, ADO.NET, 775, 777 modules, 12, 484 asynchronous delegate call, 342–344 multifile, 484–485, 496–498 asynchronous I/O, 685–686 .NET Framework Configuration utility, 502–504, asynchronous multithreading using delegates, 515 342–344 netmodule file extension, 485, 496–497 AsyncPattern property, 887 overview, 11, 475 AsyncResult class, multithreaded applications, 592 private, 499–500 attribute-based programming probing process, 499–500 assembly, module level attributes, 549 publisher policy assemblies, 518–519 AttributeUsage attribute, 548–549 referencing external, 28 C# attribute notation, 546 satellite assemblies, 484 CLSCompliant attribute, 543 self-describing, 481 COM vs. .NET attributes, 542 shared assemblies, 504, 511 constructor parameters, 545 configuration, 512 custom attributes, 546 and the GAC, 509 description, 542 single-file, 12, 484, 486, 488–489 DllImport attribute, 543 strong name code example, 507 early binding, 550–551 strong names, 481, 490, 505–508 example of custom attributes, 547–548 type metadata, 481, 484, 491 extensibility, 553–558 updating applications using shared assemblies, late binding, 551–553 512 multiple attributes, 544–545 version number, 481 NonSerialized attribute, 543 Visual Studio 2008 configuration, 502 Obsolete attribute, 543, 545–546 Win32 file header, 482 overview, 523 Windows Presentation Foundation (WPF) restricting attributes, 548–549 Application class, 1006 Serializable attribute, 543 overview, 1005 serializing example, 544 Window class, 1006–1010 summary of attribute key points, 546 element, 1191 Visual Basic snap-in example, 555 ■INDEX 1337

WebMethod attribute, 543 BasicSelections( ) method, 466 Windows forms example, 556–557 *.bat file, 72 attributes, assembly, module level, 550 Beep( ) property, 76 Attributes property, FileSystemInfo class, 663 BeginAnimation( ) method, 1139, 1141 AttributeUsage attribute, 548–549 BeginCatchBlock( ) method, authentication element, Web.config, 1204 System.Reflection.Emit.ILGenerator, 650 authorization element, Web.config, 1204 BeginClose( ) method, 892 Authorization property, 892 BeginEdit( ) method, 805 AutoDispatch value, ClassInterfaceType Begin/End asynchronous invocation pattern, 909 enumeration, 1308 BeginExceptionBlock( ) method, AutoDual value, ClassInterfaceType enumeration, System.Reflection.Emit.ILGenerator, 650 1308 BeginFinallyBlock( ) method, AutoEventWireUp attribute, ASP.NET, 1201–1202 System.Reflection.Emit.ILGenerator, 650 autogenerated, 1025 BeginInvoke( ), multithreaded applications, AutoIncrement property, 787–788 587–588, 591–593 AutoIncrementSeed property, 787–788 BeginLabel( ) method, AutoIncrementStep property, 787–788 System.Reflection.Emit.ILGenerator, 650 AutoLotConnectedLayer namespace, 780 BeginLocal( ) method, AutoLotDAL namespace, 834 System.Reflection.Emit.ILGenerator, 650 AutoLotDAL.AutoLotDataSetTableAdapters BeginOpen( ) method, 892 at faster it Find namespace, 834 BeginScope( ) method, AutoLotDAL.dll assembly, 809, 811, 813, 834, 840, System.Reflection.Emit.ILGenerator, 650 911, 1180 element, 1141 AutoLotDAL.dll library, 783, 844, 1187 BeginTime property, 1138 AutoLotDatabase class, 846, 852 BeginTransaction( ) method, DbConnection,

AutoLotDatabase namespace, 849 ADO.NET, 757 http://superindex.apress.com/ AutoLotDataSet type, 832 element, 893, 895 autoLotDB.cs file, 849 behaviorConfiguration attribute, 894 autoLotDS object, 816 Bin folder, 1190–1191 AutoLotService type, 912 Binary Application Markup Language (BAML), Automatic property, 907 1026–1028 AutoPostBack property, ASP.NET web controls, binary code reuse, 475 1208–1209 binary formats, 1316 AutoResetEvent type, 931 binary opcodes, 619–620 AutoReverse property, 1138, 1140 binary operators, overloading, 389–391 binary resources, 1145 B BinaryFormatter BackColor property, WebControl base class, 1213 deserialization, 719 Background property, 1108, 1148, 1154 serialization, 717 BackgroundWorker component, 612, 614–615 BinaryFormatter type, 714, 797 *.baml files, 1026 BinaryReader class, 682 base class, 186, 192–194, 1193 BinaryWriter class, 682 base class/derived class casting rules binding, 880, 889, 1103, 1111 as keyword, 211 element, 893, 900 is keyword, 211–212 BindingSource component, 824 overview, 210–211 Bitmap type, 986 base keyword, 192, 1254 BitmapImage object, 1129 BaseAddresses property, 892 black box programming, 165 baseAddresses scope, 891 blue screen of death, 1001 region, 891 section, 1168, 1170 BaseDirectory( ) method, System.AppDomain, 572 booc compilers, 1321 BasedOn property, 1150 bool data type, 80 BasePriority, ProcessThread type, 568 bool keyword, 82 BaseStream property, 682 Boolean member variable, 1026 basicHttpBinding bindings, 899 Border control, 1056 BasicHttpBinding class, 882, 900 BorderColor property, WebControl base class, 1213 BasicHttpBinding option, 882 BorderStyle property, WebControl base class, 1213 subelement, 900 BorderWidth property, WebControl base class, 1213 1338 ■INDEX

Bottom property, 1086 C Bounds property, C language deficiencies, 4 System.Windows.Media.Geometry base C# programming tools, 1319 class, 1131 C++ language deficiencies, 4 boxing and unboxing code comment, XML Elements, 177 CIL code, 318–319 cache mechanism, 1263 generics issues, 316, 318–319 Cache member variable, 1260 InvalidCastException, 319 Cache object, 1259 .NET 1.1 solution, 319, 321–322 Cache property, HttpResponse Type, 1198 .NET 2.0 solution, 323 Cache property, Page Type, 1195 bread crumbs, 1221 CacheDependency type, 1261 break keyword, 101 CacheItemRemovedCallback delegate target Browser Applications (XBAPs), XAML, 1004 method, 1263 Browser member, HttpRequest Type, 1196 CacheItemRemovedCallback delegate type, 1261 Browser property, 1196 Cache.NoSlidingExpiration field, 1261 browser statistics in HTTP Request processing, CacheState web application, 1259 ASP.NET, 1196 CalcInteropAsm.dll interop assembly, 1297 browser-based presentation layers, 1163 Calculate Permissions button, 708 Brush property, 1132 CalculateAverage( ) method, 111 Brush type, 989, 1036 callback interfaces Brushes utility class, 989 event interface, 305, 307–308 btnClickMe type, 1148 overview, 341 btnClickMeToo type, 1148 sink object, 305, 307–308 btnExitApp_Clicked( ) method, 1025 two-way conversation, 304 btnGetColor Button object, 1079 CallbackContract property, 887 btnGetColor_Click( ) method, 1079 CanBeNull property, 848 btnGetGameSystem property, 1078 CancelButton property, 971, 981 btnShowAppVariables Button type, 1256 CancelEdit( ) method, 805 bubbling event, 1065 CancelEventArgs property, 974 BufferedGraphics type, 987 CancelEventHandler delegate, 973 BufferedStream type, input/output, System.IO, 661 CanExecute event, 1101 BufferHeight property, 76 CanHelpExecute( ) method, 1102 BufferWidth property, 76 Canvas control, 1056 bugs, description, 219 Canvas panel control, 1084 BuildCar.aspx content page, 1228 Canvas type, 1092 BuildMenuSystem( ) helper function, 960 tag, 1085 BuildTableRelationship( ) method, 817 Canvas.Bottom property, 1086 built-in style engine, 1002 Canvas.Left property, 1086 business process, 917, 925 Canvas.Left value, 1086 Button Click event handler, 1083 Canvas.Right property, 1086 Button control, 1056, 1058, 1065 Canvas.Top property, 1086 Button object, 1312 Canvas.Top value, 1086 Button property, 975–976 Caption property, 787–788 Button type, 612, 707, 817, 950, 1022, 1078, 1081, CaretIndex property, 1082 1108, 1138, 1142, 1148, 1150, 1154, carIDColumn DataColumn object, 793 1156–1157, 1182–1183, 1188, 1234, 1240, carIDColumn object, 789 1248, 1258, 1274 carInventoryGridView object, 808 Button widget, 1210 CarLibrary.dll assembly, 691