Common Language Runtime

common language runtime and its role in .net framework and common language runtime debugging services application has generated
GregDeamons Profile Pic
GregDeamons,New Zealand,Professional
Published Date:03-08-2017
Your Website URL(Optional)
Comment
Syme_850-4C17.fm Page 491 Tuesday, October 23, 2007 1:31 PM CHA P TE R 1 7 ■ ■ ■ Interoperating with C and COM Software integration and reuse is becoming one of the most relevant activities of software development nowadays. In this chapter, we discuss how F programs can interoperate with the outside world, accessing code available in the form of DLLs and COM components. Common Language Runtime Libraries and binary components provide a common way to reuse software; even the simplest C program is linked to the standard C runtime to benefit from core functions such as memory management and I/O. Modern programs depend on a large number of libraries that are shipped in binary form, and only some of them are written in the same language of the program. Libraries can be linked statically during compilation into the executable or can be loaded dynamically during program execution. Dynamic linking has become significantly common to help share code (dynamic libraries can be linked by different programs and shared among them) and adapt program behavior while executing. Interoperability among binaries compiled by different compilers, even of the same language, can be a nightmare. One of the goals of the .NET initiative was to ease this issue by introducing the Common Language Runtime (CLR), which is targeted by different compilers and different languages to help interoperability among software developed in those languages. The CLR is a runtime designed to run programs compiled for the .NET platform. The binary format of these programs differs from the traditional one adopted by executables; according to the Microsoft terminology, we will use the term managed for the first class of programs and unmanaged otherwise (see Figure 17-1). 491Syme_850-4C17.fm Page 492 Tuesday, October 23, 2007 1:31 PM 492 CHAPTER 17 ■ INTEROPERATING WITH C AND COM Figure 17-1. Compilation scheme for managed and unmanaged code A DEEPER LOOK INSIDE .NET EXECUTABLES Programs for the .NET platform are distributed in a form that is executed by the CLR. Binaries are expressed in an intermediate language that is compiled incrementally by the Just-In-Time (JIT) compiler during program execution. A .NET assembly, in the form of a .dll or an .exe file, contains the definition of a set of types and the definition of the method bodies, and the additional data describing the structure of the code in the inter- mediate language form is known as metadata. The intermediate language is used to define method bodies based on a stack-based machine, where operations are performed by loading values on a stack of operands and then invoking methods or operators. Consider the following simple F program: open System let i = 2 Console.WriteLine("Input a number:") let v = Int32.Parse(Console.ReadLine()) Console.WriteLine(i v) The F compiler generates an executable that can be disassembled using the ildasm.exe tool distrib- uted with the .NET Framework. The following screenshot shows the structure of the generated assembly. Since everything in the CLR is defined in terms of types, the F compiler must introduce the class Hw (named after the file name Hw.fs) in the StartupCode namespace. In this class, there is the definition of the _main static method that is the entry point for the execution of the program. This is the method that will contain the intermediate language corresponding to the example F program. The F compiler generates several elements that are not defined in the program, whose goal is to preserve the semantics of the F program in the intermediate language.Syme_850-4C17.fm Page 493 Tuesday, October 23, 2007 1:31 PM CHAPTER 17 ■ INTEROPERATING WITH C AND COM 493 If you open the _main method, you’ll find the following code that we have annotated with the corresponding F statements: .method public static void _main() cil managed .entrypoint // Code size 56 (0x38) .maxstack 4 // let i = 2;; IL_0000: ldc.i4.2 IL_0001: stsfld int32 'StartupCode'.Hw::i3 // do Console.WriteLine("Input a number:");; IL_0006: ldstr "Input a number:" IL_000b: call void mscorlibSystem.Console::WriteLine(string) IL_0010: ldnull // let v = Int32.Parse(Console.ReadLine());; IL_0011: stsfld class fslibMicrosoft.FSharp.Core.Unit 'StartupCode'.Hw::_doval66 IL_0016: call string mscorlibSystem.Console::ReadLine() IL_001b: call int32 mscorlibSystem.Int32::Parse(string) IL_0020: stsfld int32 'StartupCode'.Hw::v8 // do Console.WriteLine(i v);; IL_0025: ldc.i4.2 IL_0026: call int32 Hw::get_v() IL_002b: mul IL_002c: call void mscorlibSystem.Console::WriteLine(int32)Syme_850-4C17.fm Page 494 Tuesday, October 23, 2007 1:31 PM 494 CHAPTER 17 ■ INTEROPERATING WITH C AND COM IL_0031: ldnull IL_0032: stsfld class fslibMicrosoft.FSharp.Core.Unit 'StartupCode'.Hw::_doval1111 IL_0037: ret // end of method Hw::_main The ldxxx instructions are used to load values onto the operand’s stack of the abstract machine, and the stxxx instructions store values from that stack in locations (locals, arguments, or class fields). In this example, variables are declared as top level, and the compiler introduces static fields into the Hw class. The first assignment requires the value 2 to be loaded onto the stack using the ldc instruction, and the stfld instruction stores the value in the static variable that represents i in the compiled program. For method invo- cations, arguments are loaded on the stack, and a call operation is used to invoke the method. The JIT compiler is responsible for generating the binary code that will run on the actual processor. The code generated by the JIT interacts with all the elements of the runtime, including external code loaded dynamically in the form of DLLs or COM components. Since the F compiler targets the CLR, its output will be managed code, allowing compiled programs to interact directly with other programming languages targeting the .NET platform. We already showed how to exploit this form of interoperability in Chapter 10, when we showed how to develop a graphic control in F and use it in a C application. Memory Management at Run Time Interoperability of F programs with unmanaged code requires an understanding of the structure of the most important elements of a programming language’s runtime. In particular, you must consider how program memory is organized at run time. Memory used by a program is generally classified in three classes depending on the way it is handled: � Static memory, allocated for the entire lifetime of the program � Automatic memory, allocated and freed automatically when functions or methods are executed � Dynamic memory, explicitly allocated by the program and freed explicitly or by an auto- matic program called the garbage collector As a rule of thumb, top-level variables and static fields belong to the first class, function arguments and local variables belong to the second class, and memory explicitly allocated using the new operator belongs to the last class. The code generated by the JIT compiler uses different data structures to manage memory and automatically interacts with the operating system to request and release memory during program execution. Each execution thread has a stack where local variables and arguments are allocated when a function or method is invoked (see Figure 17-2). A stack is used because it naturally follows the execution flow of method and function calls. The topmost record contains data about the currently executing function; below that is the record of the caller of the function, which sits on top of another record of its caller, and so on. These activation records are memory blocks used to hold the memory required during the execution of the function and are naturally freed at the Syme_850-4C17.fm Page 495 Tuesday, October 23, 2007 1:31 PM CHAPTER 17 ■ INTEROPERATING WITH C AND COM 495 end of its execution by popping the record out of the stack. The stack data structure is used to implement the automatic memory of the program, and since different threads execute different functions at the same time, a separate stack is assigned to each of them. Figure 17-2. Memory organization of a running CLR program Dynamic memory is allocated in the heap, which is a data structure where data resides for an amount of time not directly related to the events of program execution. The memory is explicitly allocated by the program, and it is deallocated either explicitly or automatically depending on the strategy adopted by the run time to manage the heap. In the CLR, the heap is managed by a garbage collector, which is a program that tracks memory usage and reclaims memory that is no longer used by the program. Data in the heap is always referenced from the stack—or other known areas such as static memory—either directly or indirectly. The garbage collector can deduce the memory potentially reachable by program execution in the future, and the remaining memory can be collected. During garbage collection, the running program may be suspended since the collector may need to manipulate objects needed by its execution. In particular, a garbage collector may adopt a strategy named copy collection that can move objects in memory, and during this process, the references may be inconsistent. To avoid dangling references, the memory model adopted by the CLR requires that methods access the heap through object references stored on the stack, and objects in the heap are forbidden to reference data on the stack. Data structures are the essential tool provided by programming languages to group values. A data structure is rendered as a contiguous area of memory in which the constituents are available at a given offset from the beginning of the memory. The actual layout of an object is determined by the compiler (or by the interpreter for interpreted languages) and is usually irrelevant to the program since the programming language provides operators to access fields without having to explicitly access the memory. System programming, however, often requires explicit manip- ulation of memory, and programming languages such as C allow controlling the in-memory Syme_850-4C17.fm Page 496 Tuesday, October 23, 2007 1:31 PM 496 CHAPTER 17 ■ INTEROPERATING WITH C AND COM layout of data structures. The C specification, for instance, defines that fields of a structure are laid out sequentially, though the compiler is allowed to insert extra space between them. Padding is used to align fields at word boundaries of the particular architecture in order to optimize the access to the fields of the structure. Thus, the same data structure in a program may lead to different memory layout depending on the strategy of the compiler or the runtime, even in a language such as C where field ordering is well-defined. By default, the CLR lays out structures in memory without any constraint, which gives the freedom of optimizing memory usage on a particular architecture, though it may introduce interoperability issues if a portion of memory 1 must be shared among the runtimes of different languages. Interoperability among different programming languages revolves mostly around memory layouts, since the execution of a compiled routine is a jump to a memory address. But routines access memory explicitly, expecting that data is organized in a certain way. In the rest of this chapter, we discuss the mechanisms used by the CLR to interoperate with external code in the form of DLLs or COM components. COM Interoperability Component Object Model (COM) is a technology that Microsoft introduced in the 1990s to support interoperability among different programs possibly developed by different vendors. The Object Linking and Embedding (OLE) technology that allows embedding arbitrary content in a Microsoft Word document, for instance, relies on this infrastructure. COM is a binary standard that allows code written in different languages to interoperate, assuming that the programming language supports this infrastructure. Most of the Windows operating system and its applica- tions are based on COM components. The CLR was initially conceived as an essential tool to develop COM components, being that COM was the key technology at the end of 1990s. It is no surprise that the Microsoft imple- mentation of CLR interoperates easily and efficiently with the COM infrastructure. In this section, we briefly review the main concepts of the COM infrastructure and its goals in order to show you how COM components can be consumed from F (and vice versa) and how F components can be exposed as COM components. A COM component is a binary module with a well-defined interface that can be dynami- cally loaded at run time by a running program. The COM design was influenced by CORBA and the Interface Definition Language (IDL) to describe a component as a set of interfaces. In the case of COM, however, components are always loaded inside the process using the dynamic loading of DLLs. Even when a component runs in a different process, a stub is loaded as a DLL, and it is responsible for interprocess communication. When you create an instance of a COM component, you obtain a pointer to an IUnknown interface that acts as the entry point to all interfaces implemented by the component. The QueryInterface method of this interface allows you to get pointers to additional interfaces. Interface pointers in COM are pointers to tables of pointers defining the method’s loca- tion. The program must know the layout of the table in order to read the desired pointer and invoke the corresponding method. This knowledge can be compiled into the program (interfaces 1. Languages targeting .NET are not affected by these interoperability issues since they share the same CLR runtime.Syme_850-4C17.fm Page 497 Tuesday, October 23, 2007 1:31 PM CHAPTER 17 ■ INTEROPERATING WITH C AND COM 497 must be known at compile time) or acquired at run time by accessing component metadata in the form of an interface named IDispatch or a database called type library. Since COM components can be compiled by any compiler supporting the generation of memory layouts compatible with the standard, it is necessary that the client shares the same layout for data structures that must be passed or returned by the component methods. The standard type system for COM, defined in ole.dll, defines a simple and restricted set of types. COM types correspond to the Variant type of Visual Basic and provide only basic types and arrays. For structured types, COM requires a custom marshaller to be developed, but this has been rarely used in components that are widely available. The COM infrastructure provides a memory manager that uses reference counting to auto- matically free components when they are not used anymore. Whenever a copy of a pointer to an interface is copied, the programmer is required to invoke the AddRef method of the IUnknown interface (every interface inherits from IUnknown), and when the pointer is no longer required, the Release method should be called to decrement the counter inside the component. When the counter reaches zero, the component is automatically freed. This strategy of memory manage- ment, though more automatic than the traditional malloc/free handling of the heap, has proven to be error prone, because programmers often forget to increment the counter when pointers are copied (risk of dangling pointers) or decrement when a pointer is no longer needed (risk of memory wasted in garbage). When Microsoft started developing the runtime that has become the CLR, which was doomed to replace the COM infrastructure, several design goals addressed common issues of COM development: Memory management: Reference counting has proven to be error prone; so a fully auto- mated memory manager was needed to address this issue. Pervasive metadata: The COM type system was incomplete, and the custom marshaller was too restrictive. A more complete and general type system whose description was avail- able at run time would have eased interoperability. Data and metadata separation: The separation between data and metadata has proven to be fragile because components without their description are useless, and vice versa. A binary format containing both components and their descriptions avoids these issues. Distributed components: DCOM, the distributed COM infrastructure, has proven to be inefficient. The CLR has been designed with a distributed memory management approach to reduce the network overhead required to keep remote components alive. The need for better component infrastructure led Microsoft to create the CLR, but the following concepts from COM proved so successful that they motivated several aspects of the CLR: Binary interoperability: The ability to interoperate at the binary level gives you the freedom to develop components from any language supporting the component infrastructure, allowing, for instance, Visual Basic developers to benefit from C++ components, and vice versa. Dynamic loading: The interactive dynamic loading of components is an essential element to allow scripting engines such as Visual Basic for Applications to access the component model. Reflection: The component description available at run time allows programs to deal with unknown components; this is especially important for programs featuring scripting envi- ronments as witnessed by the widespread use of IDispatch and type libraries in COM.Syme_850-4C17.fm Page 498 Tuesday, October 23, 2007 1:31 PM 498 CHAPTER 17 ■ INTEROPERATING WITH C AND COM COM METADATA AND WINDOWS REGISTRY COM components are compiled modules that conform to well-defined protocols designed to allow binary interoper- ability among different languages. An essential trait of component architectures is the ability to dynamically create components at run time. It is necessary for an infrastructure to locate and inspect components in order to find and load them. The Windows registry holds this information, which is why it is such an important struc- ture in the operating system. The HKEY_CLASSES_ROOT registry key holds the definition of all the components installed on the local computer. It is helpful to understand the basic layout of the registry in this respect when dealing with COM components. The following is a simple script in Jscript executed by the Windows Scripting Host, which is an interpreter used to execute Visual Basic and Jscript scripts on Windows: w = WScript.CreateObject("Word.Application"); w.Visible = true; WScript.Echo("Press OK to close Word"); w.Quit(); This simple script creates an instance of a Microsoft Word application and shows programmatically its window by setting the Visible property to true. Assuming that the script is executed using the wscript command (the default), its execution is stopped until the message box displayed by the Echo method is closed, and then Word is closed. How can the COM infrastructure dynamically locate the Word component and load without prior knowledge about it? The COM infrastructure is accessed through the ubiquitous CreateObject method that accepts a string as input that contains the program ID of the COM component to be loaded. This is the human-readable name of the component, but the COM infrastructure was conceived as a foundation of a potentially large number of components and therefore adopted the global unique identifier (GUID) strings to define components. GUIDs are often displayed during software installation and are familiar for their mysterious syntax of a sequence of hexa- decimal digits enclosed in curly braces. These GUIDs are also used to identify COM classes; these IDs are known as CLSIDs and are stored in the Windows registry as subkeys containing further metadata about the COM object. When CreateObject is invoked, the code infrastructure looks for the key: HKEY_CLASSES_ROOT\Word.Application\CLSID The default value for the key in this example (on one of our computers) is as follows: 000209FF-0000-0000-C000-000000000046 Now you can access the registry key defining the COM component and find all the information relative to the component. The following screenshot shows the content of the LocalServer32 subkey, where it says that winword.exe is the container of the Word.Application component. If a COM component should be executed in a process different from that of the creator, LocalServer32 contains the location of the execut- able. Components are often loaded in-process in the form of a DLL, and in this case it is the InprocServer32 key that indicates the location of the library.Syme_850-4C17.fm Page 499 Tuesday, October 23, 2007 1:31 PM CHAPTER 17 ■ INTEROPERATING WITH C AND COM 499 To get a feel for the number of COM components installed on a Windows system, you can use a few lines of F using fsi.exe as a shell: open Microsoft.Win32;; let k = Registry.ClassesRoot.OpenSubKey("CLSID");; val k : RegistryKey k.GetSubKeyNames().Length;; val it : int = 9237 k.Close();; val it : unit = () The registry is also responsible for storing information (that is, the metadata associated with COM components that describes their interfaces) about type libraries. Type libraries are also described using registry entries and identified by GUIDs available under the registry key: HKEY_CLASSES_ROOT\TypeLib COM components can be easily consumed from F programs, and the opposite is also possible by exposing .NET objects as COM components. The following example is similar to the one discussed in the “COM Metadata and Windows Registry” sidebar; it is based on the Windows Scripting Host but uses F and fsi.exe:Syme_850-4C17.fm Page 500 Tuesday, October 23, 2007 1:31 PM 500 CHAPTER 17 ■ INTEROPERATING WITH C AND COM open System;; let o = Activator.CreateInstance(Type.GetTypeFromProgID("Word.Application"));; val o : obj let t = o.GetType();; val t : Type t.GetProperty("Visible").SetValue(o, (true : Object), null);; val it : unit = () let m = t.GetMethod("Quit");; val m : Reflection.MethodInfo m.GetParameters().Length;; val it : int = 3 m.GetParameters();; val it : ParameterInfo = System.Object& SaveChanges Attributes = In, Optional, HasFieldMarshal; DefaultValue = System.Reflection.Missing; IsIn = true; IsLcid = false; IsOptional = true; IsOut = false; IsRetval = false; Member = Void Quit(System.Object ByRef, System.Object ByRef, System.Object ByRef); MetadataToken = 134223449; Name = "SaveChanges"; ParameterType = System.Object&; Position = 0; RawDefaultValue = System.Reflection.Missing;; ... more ... m.Invoke(o, null; null; null );; val it : obj = null Since F imposes type inference, you cannot use the simple syntax provided by an inter- preter. The compiler should know in advance the number and type of arguments of a method and the methods exposed by an object. You must remember that even if fsi.exe allows you to interactively execute F statements, it still is subjected to the constraints of a compiled language. Since you are creating an instance of a COM component dynamically in this example, the compiler does not know anything about this component. Thus, it can be typed as just System.Object. To obtain the same behavior of an interpreted language, you must resort to the reflection support of the .NET runtime. Using the GetType method, you can obtain an object describing the type of the object o. Then you can obtain a PropertyInfo object describing the Visible property, and you can invoke the SetValue method on it to show the Word main window. The SetValue method is generic; therefore, you have to cast the Boolean value to System.Object to comply with the method signature.Syme_850-4C17.fm Page 501 Tuesday, October 23, 2007 1:31 PM CHAPTER 17 ■ INTEROPERATING WITH C AND COM 501 In a similar way, you can obtain an instance of the MethodInfo class describing the Quit method. Since a method has a signature, you ask for the parameters; there are three of them, and they are optional. Therefore, you can invoke the Quit method by calling the Invoke method and passing the object target of the invocation and an array of arguments that you set to null because arguments are optional. How can the runtime interact with COM components? The basic approach is based on the so-called COM callable wrapper (CCW) and the runtime callable wrapper (RCW), as shown in Figure 17-3. The former is a chunk of memory dynamically generated with a layout compatible with the one expected from COM components so that external programs, even legacy Visual Basic 6 applications, can access services implemented as managed components. The latter is more common and creates a .NET type dealing with the COM component, taking care of all the interoperability issues. It is worth noting that although the CCW can always be generated because the .NET runtime has full knowledge about assemblies, the opposite is not always possible. Without IDispatch or type libraries, there is no description of a COM component at run time. Moreover, if a component uses custom marshalling, it cannot be wrapped by an RCW. Fortunately, for the majority of COM components, it is possible to generate the RCW. Figure 17-3. The wrappers generated by the CLR to interact with COM components Programming patterns based on event-driven programming are widely adopted, and COM components have a programming pattern to implement callbacks based on the notion of sink. The programming pattern is based on the delegate event model, and the sink is where a listener can register a COM interface that should be invoked by a component to notify an event. The Internet Explorer Web Browser COM component (implemented by shdocvw.dll), for instance, provides a number of events to notify its host about the various events such as loading a page or clicking a hyperlink. The RCW generated by the runtime exposes these events in the form of delegates and takes care of handling all the details required to perform the communication between managed and unmanaged code. Although COM components can be accessed dynamically using .NET reflection, explicitly relying on the ability of the CLR to generate CCW and RCW, it is desirable to use a less verbose Syme_850-4C17.fm Page 502 Tuesday, October 23, 2007 1:31 PM 502 CHAPTER 17 ■ INTEROPERATING WITH C AND COM approach to COM interoperability. The .NET runtime ships with tools that allow you to generate RCW and CCW wrappers offline, which allows you to use COM components as .NET classes, and vice versa. These tools are as follows: tlbimp.exe: This is a tool for generating an RCW of a COM component given its type library. 2 aximp.exe: This is similar to tlbimp.exe and supports the generation of ActiveX components that have graphical interfaces (and that need to be integrated with Windows Forms). tlbexp.exe: This generates a COM type library describing a .NET assembly. The CLR will be loaded as a COM component and will generate the appropriate CCW to make .NET types accessible as COM components. regasm.exe: This is similar to tlbexp.exe. It also performs the registration of the assembly as a COM component. To better understand how COM components can be accessed from your F programs, and vice versa, consider two examples: in the first one, you will wrap the widely used Flash Player into a form interactively, and in the second one, you will see how an F object type can be consumed as if it were a COM component. The Flash Player that you are accustomed to using in your everyday browsing is an ActiveX control that is loaded by Internet Explorer using an OBJECT element in the HTML page (it is also a plug-in for other browsers, but here we are interested in the COM component). By using a search engine, you can easily find that an HTML element similar to the following is used to embed the player in Internet Explorer: OBJECT classid ="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" width ="640" height="480" title ="My movie" param name="movie" value="MyMovie.swf" / param name="quality" value="high" / /OBJECT From this tag, you know that the CLSID of the Flash Player ActiveX component is the one specified with the classid parameter of the OBJECT element. You can now look in the Windows registry under HKEY_CLASSES_ROOT\CLSID for the subkey corresponding to the CLSID of the Flash ActiveX control. If you look at the subkeys, you notice that the ProgID of the component is ShockwaveFlash.ShockwaveFlash, and InprocServer32 indicates that its location is C:\Windows\ system32\Macromed\Flash\Flash9b.ocx. You can also find the GUID relative to the component type library that, when investigated, shows that the type library is contained in the same OCX file. Since Flash Player is an ActiveX control with a GUI, you can rely on aximp.exe rather than just tlbimp.exe to generate the RCW for the COM component: 2. ActiveX components are COM components implementing a well-defined set of interfaces, and they have a graphical interface. Internet Explorer is well known for loading these components, but ActiveX can be loaded by any application using the COM infrastructure.Syme_850-4C17.fm Page 503 Tuesday, October 23, 2007 1:31 PM CHAPTER 17 ■ INTEROPERATING WITH C AND COM 503 C:\ aximp c:\Windows\System32\Macromed\Flash\Flash9b.ocx Generated Assembly: C:\ShockwaveFlashObjects.dll Generated Assembly: C:\AxShockwaveFlashObjects.dll If you use ildasm.exe to analyze the structure of the generated assemblies, you’ll notice that the wrapper of the COM component is contained in ShockwaveFlashObjects.dll and is generated by the tlbimp.exe tool; the second assembly simply contains a Windows Forms host for ActiveX components and is configured to host the COM component, exposing the GUI features in terms of the elements of the Windows Forms framework. You can test the Flash Player embedded in an interactive F session: I "c:\\" Added 'c:\ ' to library include path r "AxShockwaveFlashObjects.dll";; Referenced 'c:\AxShockwaveFlashObjects.dll' open AxShockwaveFlashObjects;; open System.Windows.Forms;; let f = new Form();; val f : Form let flash = new AxShockwaveFlash();; val flash : AxShockwaveFlash Binding session to 'c:\AxShockwaveFlashObjects.dll'... f.Show();; val it : unit = () flash.Dock - DockStyle.Fill;; val it : unit = () f.Controls.Add(flash);; val it : unit = () flash.LoadMovie(0, "http://laptop.org/img/meshDemo18.swf");; val it : unit = () Here you first add to the include path of the fsi.exe directory containing the assemblies generated by aximp.exe using the I directive, and then you reference the AxShockwaveFlashObjects.dll assembly using the r directive. The namespace AxShockwaveFlashObjects containing the AxShockwaveFlash class is opened; this is the managed class wrapping the ActiveX control. You create an instance of the Flash Player that is now exposed as a Windows Forms control; then you set the Dock property to DockStyle.Fill to let the control occupy the entire area of the form, and finally you add the control to the form. When typing the commands into F Interactive, it is possible to test the content of the form. When it first appears, a right-click on the client area is ignored. After the ActiveX control is added to the form, the right-click displays the context menu of the Flash Player. You can now programmatically control the player by setting the properties and invoking its methods; the generated wrapper will take care of all the communications with the ActiveX component. Now we’ll show an example of exposing an F object type as a COM component. There are several reasons it can be useful to expose a managed class as a COM component, but perhaps Syme_850-4C17.fm Page 504 Tuesday, October 23, 2007 1:31 PM 504 CHAPTER 17 ■ INTEROPERATING WITH C AND COM the most important is interoperability with legacy systems. COM has been around for a decade and has permeated every aspect of Windows development. Systems have largely used COM infrastructures, and they can also be extended using this technology. COM components are heavily used by applications based on the Active Scripting architecture such as ASP or VBA in Microsoft Office. The ability of exposing F code to existing applications is useful because it allows you to immediately start using this new language and integrating it seamlessly into existing systems. In this example, suppose you are exposing a simple F object type as a COM component and invoke a method of this object type from a JScript script. You define the following type in the hwfs.fs file: open System type FSCOMComponent = new() as x = member x.HelloWorld() = "Hello world from F" The assembly that must be exposed as a COM component should be added to the global assembly cache (GAC), which is where shared .NET assemblies are stored. Assemblies present in the GAC must be strongly named, which means a public key cryptographic signature must be used to certify the assembly. To perform the test, you generate a key pair to be used to sign the assembly using the sn.exe command available with the .NET SDK: C: sn –k testkey.snk Microsoft (R) .NET Framework Strong Name Utility Version 2.0.50727.42 Copyright (c) Microsoft Corporation. All rights reserved. Key pair written to testkey.snk Now you can compile the program in a DLL called hwfs.dll: C:\ fsc -a –keyfile testkey.snk hwfs.fs Note that you use the –keyfile switch to indicate to the compiler that the output should be signed using the specified key pair. Now you can add the assembly to the GAC (note that under Windows Vista the shell used should run with administrator privileges): C:\ gacutil /i hwfs.dll Microsoft (R) .NET Global Assembly Cache Utility. Version 2.0.50727.42 Copyright (c) Microsoft Corporation. All rights reserved. Assembly successfully added to the cacheSyme_850-4C17.fm Page 505 Tuesday, October 23, 2007 1:31 PM CHAPTER 17 ■ INTEROPERATING WITH C AND COM 505 Now you can use the regasm.exe tool to register the hwfs.dll assembly as a COM component: C:\ regasm hwfs.dll Microsoft (R) .NET Framework Assembly Registration Utility 2.0.50727.312 Copyright (C) Microsoft Corporation 1998-2004. All rights reserved. Types registered successfully Now you have to write the script using the Jscript language to test the component; the script will use the CreateObject method to create an instance of the F object type, with the CCW generated by the CLR taking care of all the interoperability issues. But what is the ProgID of the COM component? You use regasm.exe with the /regfile switch to generate a registry file containing the keys corresponding to the registration of the COM component instead of performing it. The generated registry file contains the following component registration (we’ve included only the most relevant entries): HKEY_CLASSES_ROOT\Hwfs+FSCOMComponent ="Hwfs+FSCOMComponent" HKEY_CLASSES_ROOT\Hwfs+FSCOMComponent\CLSID ="41BFA014-8389-3855-BD34-81D8933045BF" HKEY_CLASSES_ROOT\CLSID\41BFA014-8389-3855-BD34-81D8933045BF ="Hwfs+FSCOMComponent" HKEY_CLASSES_ROOT\CLSID\41BFA014-8389-3855-BD34-81D8933045BF\InprocServer32 ="mscoree.dll" "ThreadingModel"="Both" "Class"="Hwfs+FSCOMComponent" "Assembly"="hwfs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=97db6c0b1207bed4" "RuntimeVersion"="v2.0.50727" The InprocServer32 subkey indicates that the COM component is implemented by mscoree.dll, which is the CLR, and the additional attributes indicate the assembly that should be run by the runtime. Note that the ProgID and the class name of the component is Hwfs+FSCOMComponent, which is partly derived from the namespace Hwfs generated by the F compiler. You can now try to write the following script in the hwfs.js file: o = WScript.CreateObject("Hwfs+FSCOMComponent"); WScript.Echo(o.HelloWorld()); If you execute the script (here using the command-based host cscript), you obtain the expected output:Syme_850-4C17.fm Page 506 Tuesday, October 23, 2007 1:31 PM 506 CHAPTER 17 ■ INTEROPERATING WITH C AND COM C:\ cscript foo.js Microsoft (R) Windows Script Host Version 5.7 Copyright (C) Microsoft Corporation. All rights reserved. Hello world from F But how can you obtain a ProgID with dot-notation instead of the ugly plus sign? So far, you have used only the basic features of the COM interoperability, but a number of custom attributes can give you finer control over the CCW generation. These attributes are defined in the System.Runtime.InteropServices namespace; and among these classes you’ll find the ProgIdAttribute class whose name hints that it is somewhat related to the ProgID. In fact, you can annotate your F object type using this attribute: open System open System.Runtime.InteropServices ProgId("Hwfs.FSComponent") type FSCOMComponent = new() as x = member x.HelloWorld() = "Hello world from F" First unregister the previous component: C:\ regasm hwfs.dll /unregister Microsoft (R) .NET Framework Assembly Registration Utility 2.0.50727.312 Copyright (C) Microsoft Corporation 1998-2004. All rights reserved. Types un-registered successfully C:\ gacutil /u hwfs Microsoft (R) .NET Global Assembly Cache Utility. Version 2.0.50727.42 Copyright (c) Microsoft Corporation. All rights reserved. Assembly: hwfs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=8c1f06f522fc70f8, processorArchitecture=MSIL Uninstalled: hwfs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=8c1f06f522fc70f8, processorArchitecture=MSIL Number of assemblies uninstalled = 1 Number of failures = 0 Now you can update the script as follows and register everything again after recompiling the F file: o = WScript.CreateObject("Hwfs.FSComponent"); WScript.Echo(o.HelloWorld()); Using other attributes, it is possible to specify the GUIDs to be used and several other aspects that are important in some situations. When a system expects a component implementing a Syme_850-4C17.fm Page 507 Tuesday, October 23, 2007 1:31 PM CHAPTER 17 ■ INTEROPERATING WITH C AND COM 507 given COM interface, it is expected that the COM component returns the pointer to the inter- face with the appropriate GUID; in this case, the ability to explicitly indicate a GUID is essential to defining a .NET interface that should be marshalled as the intended COM interface. Complex COM components can be tricky to wrap using these tools, and official wrappers are maintained by component developers. Microsoft provides the managed version of the Office components, the managed DirectX library, and the Web Browser control to spare programmers from having to build their own wrappers. In conclusion, it is possible to use COM components from F, and vice versa. You can expose F libraries as COM components, which allows you to extend existing systems using F, or use legacy code in F programs Platform Invoke COM interoperability is an important area of interoperability in F, but it is limited to Windows and to the Microsoft implementation of the ECMA and ISO standards of the Common Language Infrastructure (CLI). The CLI standard, however, devises a standard mechanism for interoper- ability that is called Platform Invoke (PInvoke to friends), and it is a core feature of the standard available on all CLI implementations, including Mono. The basic model underlying PInvoke is based on loading dynamic linking libraries into the program, which allows managed code to invoke exported functions. Dynamic linking libraries do not provide information other than the entry point location of a function; this is not enough to perform the invocation unless additional information is made available to the runtime. The invocation of a function requires the following: � The address of the code in memory � The calling convention, which is how parameters, return values, and other information is passed through the stack to the function � Marshalling of values and pointers so that the different runtime support can operate consistently on the same values The address of the entry point is obtained using a system call that returns the pointer to the function given a string. The remaining information must be provided by the programmer to instruct the CLR about how the function pointer should be used. CALLING CONVENTIONS Function and method calls (a method call is similar to a function call but with an additional pointer referring to the object passed to the method) are performed by using a shared stack between the caller and the callee. An activation record is pushed onto the stack when the function is called, and memory is allocated for arguments, the return value, and local variables. Additional information is also stored in the activation record, such as information about exception handling and the return address when the execution of the function terminates. The physical structure of the activation record is established by the compiler (or by the JIT in the case of the CLR), and this knowledge must be shared between the caller and the called function. When the binary code is generated by a compiler, this is not an issue, but when code generated by different compilers must interact, it may become a significant issue. Although each compiler may adopt a different convention, the need to performSyme_850-4C17.fm Page 508 Tuesday, October 23, 2007 1:31 PM 508 CHAPTER 17 ■ INTEROPERATING WITH C AND COM system calls requires that the calling convention adopted by the operating system is implemented, and it is often used to interact with different runtimes. Another popular approach is to support the calling convention adopted by C compilers because it is widely used and has become a fairly universal language for interopera- bility. Note that although many operating systems are implemented in C, the libraries providing system calls may adopt different calling conventions. This is the case of Microsoft Windows where the operating system adopts the so-called stdcall calling convention rather than the C calling convention. A significant dimension in the arena of possible calling conventions is the responsibility for removing the activation record from the thread stack. At first glance, it may seem obvious that it is the called function that before returning resets the stack pointer to the previous state. This is not the case for programming languages such as C that allow functions with a variable number of arguments such as printf. When variable argu- ments are allowed, it is the caller that knows exactly the size of the activation record; therefore, it is its respon- sibility to free the stack at the end of the function call. Apart from being consistent with the chosen convention, it may seem that there is little difference between the two choices, but if the caller is responsible for cleaning the stack, each function invocation requires more instructions, which leads to larger executables; for this reason, Windows uses the stdcall calling convention instead of the C calling convention. It is important to notice that the CLR uses an array of objects to pass a variable number of arguments, which is very different from the vari- able arguments of C because the method receives a single pointer to the array that resides in the heap. It is important to note that if the memory layout of the activation record is compatible, as it is in Windows, the use of the cdecl convention instead of the stdcall convention leads to a subtle memory leak. If the runtime assumes the stdcall convention (that is the default) and the callee assumes the cdecl convention, the argu- ments pushed on the stack are not freed, and at each invocation the height of the stack grows until a stack overflow happens. The CLR supports a number of calling conventions. The two most important are stdcall, which is the convention used by Windows APIs (and many others DLLs), and cdecl, which is the convention used by the C language. Other implementations of the runtime may provide additional conventions to the user. In the PInvoke design, there is nothing restricting the supported conventions to these two (and in fact the runtime uses the fcall convention for invoking services provided by the runtime from managed code). The additional information required to perform the function call is provided by custom attributes that are used to decorate a function prototype and inform the runtime about the signature of the exported function. Getting Started with PInvoke This section starts with a simple example of a DLL developed using C++ to which you will add code during your experiments using PInvoke. The CInteropDLL.h header file declares a macro defining the decorations associated with each exported function: define CINTEROPDLL_API __declspec(dllexport) extern "C" void CINTEROPDLL_API HelloWorld(); The __declspec directive is specific to the Microsoft Visual C++ compiler, and other compilers may provide different ways to indicate the functions that must be exported when compiling a DLL.Syme_850-4C17.fm Page 509 Tuesday, October 23, 2007 1:31 PM CHAPTER 17 ■ INTEROPERATING WITH C AND COM 509 The first function is the HelloWorld function; its definition is as expected: void CINTEROPDLL_API HelloWorld() printf("Hello C world invoked by F\n"); Say you now want to invoke the HelloWorld function from an F program. You simply have to define the prototype of the function and inform the runtime how to access the DLL and the other information needed to perform the invocation. The program performing the invocation is the following: open System.Runtime.InteropServices module CInterop = DllImport("CInteropDLL", CallingConvention=CallingConvention.Cdecl) extern void HelloWorld() CInterop.HelloWorld() The extern keyword informs the compiler that the function definition is external to the program and must be accessed through the PInvoke interface. A C-style prototype definition follows the keyword, and the whole declaration is annotated with a custom attribute defined in the System.Runtime.InteropServices namespace. The F compiler adopts C-style syntax for extern prototypes, including argument types (as you’ll see later), because C headers and proto- types are widely used, and this choice helps in the PInvoke definition. The DllImport custom attribute provides the information needed to perform the invocation. The first argument is the name of the DLL containing the function; the remaining option specifies the calling conven- tion chosen to make the call. Since not specified otherwise, the runtime assumes that the name of the F function is the same as the name of the entry point in the DLL. It is possible to over- ride this behavior using the EntryPoint parameter in the DllImport attribute. It is important to note the declarative approach of the PInvoke interface. There is no code involved in accessing external functions. The runtime interprets metadata in order to automat- ically interoperate with native code contained in a DLL. This is a different approach from the one adopted by different virtual machines such as, for example, the Java virtual machine. The Java Native Interface (JNI) requires that the programmer defines a layer of code using types of the virtual machine and invokes the native code. Platform Invoke requires high privileges in order to execute native code, because the acti- vation record of the native function is allocated on the same stack containing the activation records of managed functions and methods. Moreover, as we will discuss shortly, it is also possible to have the native code invoking a delegate marshalled as a function pointer, allowing stacks with native and managed activation records to be interleaved. The HelloWorld function is a simple case since the function does not have input arguments and does not return any value. Consider this function with input arguments and a return value: int CINTEROPDLL_API Sum(int i, int j) return i + j; Syme_850-4C17.fm Page 510 Tuesday, October 23, 2007 1:31 PM 510 CHAPTER 17 ■ INTEROPERATING WITH C AND COM Invoking the Sum function requires integer values to be marshalled to the native code and the value returned to managed code. Simple types such as integers are easy to marshal since they usually are passed by value and use types of the underlying architecture. The F program using the Sum function is as follows: module CInterop = DllImport("CInteropDLL", CallingConvention=CallingConvention.Cdecl) extern int Sum(int i, int j) printf "Sum(1, 1) = %d\n" (CInterop.Sum(1, 1)); Parameter passing assumes the same semantics of the CLR, and parameters are passed by value for value types and by the value of the reference for reference types. Again, you use the custom attribute to specify the calling convention for the invocation. Data Structures We first cover what happens when structured data gets marshalled by the CLR in the case of nontrivial argument types. Here we show the SumC function responsible for adding two complex numbers defined by the Complex C data structure: typedef struct _Complex double re; double im; Complex; Complex CINTEROPDLL_API SumC(Complex c1, Complex c2) Complex ret; ret.re = c1.re + c2.re; ret.im = c1.im + c2.im; return ret; To invoke this function from F, you must define a data structure in F corresponding to the Complex C structure. If the memory layout of an instance of the F structure is the same as that of the corresponding C structure, then values can be shared between the two languages. But how can you control the memory layout of a managed data structure? Fortunately, the PInvoke specification helps with custom attributes that allow specifying memory layout of data structures. The StructLayout custom attribute consents to indicate the strategy adopted by the runtime to lay out fields of the data structure. By default, the runtime adopts its own strategy in the attempt to optimize the size of the structure, keeping fields aligned to the machine world in order to ensure fast access to the fields of the structure. The C standard ensures that fields are laid out in memory sequentially in the order they appear in the structure definition; other languages may use different strategies. Using an appropriate argument, you can indicate that a C-like sequential layout strategy should be adopted. Moreover, it is also possible to provide an explicit layout for the structure indicating the offset in memory for each field of the structure. For this example, here we use the sequential layout for the Complex value type:

Advise: Why You Wasting Money in Costly SEO Tools, Use World's Best Free SEO Tool Ubersuggest.