Appendixes APPENDIX A
Total Page:16
File Type:pdf, Size:1020Kb
PART 8 Appendixes APPENDIX A COM and .NET Interoperability The goal of this book was to provide you with a solid foundation in the C# 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 Microsoft’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 managed code that can be hosted by the common language runtime (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 metadata, assemblies contain a manifest 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 assembly. 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 operating system (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 Visual Basic 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).