RAD Studio for Microsoft .NET
ContentsIndex
PreviousUpNext
Using COM Interop in Managed Applications

COM Interop is a .NET service that allows seamless interoperation between managed and unmanaged code. The COM Interop service is a two-way bridge: It allows you to leverage existing COM servers and ActiveX Controls in new .NET applications, as well as to expose .NET components in legacy, unmanaged applications. 

The RAD Studio IDE features tools that will help you integrate your legacy COM servers and ActiveX Controls into managed applications. Within the IDE, you can add references to unmanaged DLLs to your project, and then browse the types contained in them, just as you can with managed assemblies. You can add ActiveX Controls to the Tool Palette, and then drop them on your forms as you would with any .NET component. 

The following topics are covered in this overview:

  • Introduction to the terminology of COM Interop. If you are already familiar with these concepts, you can skip directly to the section on RAD Studio IDE features and tools for COM/Interop.
  • Introduction to some of the .NET Framework SDK tools for working with COM/Interop.
  • Using COM Interop Assemblies in the IDE.

 

Diagram 

Seamless interoperability is achieved through stand-in objects called Runtime Callable Wrappers (RCW). The RCW is a layer of communication between your managed application, and the actual unmanaged COM server.

The .NET Framework has a rich collection of terms and three-letter acronyms. This section will help you understand the terminology you will encounter when reading other COM Interop literature.

Metadata

In the context of .NET and COM, metadata is a term used to mean type information. In COM, type information can be stored in a variety of ways. For instance, a C++ header file is a language-specific container for type information. A type library is also a container for type information, but being a binary format, type libraries are language neutral. Unlike the COM development model where type libraries are not required, language neutral metadata is mandatory for all .NET assemblies. Every assembly is self-describing; its metadata contains complete type information, including private types and private class members.

Custom Attributes

Developers often tag program entities (such as classes and their methods) with descriptive attributes such as static, private, protected, and public. In the .NET Framework, you can tag any entity, including classes, properties, methods, and even assemblies themselves, with an attribute of your own design and meaning. Custom attributes are expressed in source code, and are processed by the compiler. At the end of the build process, custom attributes are emitted into the output assembly just like all metadata.

Reflection

A unique characteristic of the .NET Framework is that type information is not lost during the compilation process. Instead, all metadata, including custom attributes, is emitted by the compiler into the final output assembly. Metadata is available at runtime, through .NET Reflection services. The .NET Framework SDK provides a reflection tool called ildasm that allows the developer to open any .NET assembly, and inspect the types declared therein. Such reflection tools often allow programmers to directly view the IL code generated by the compiler. The RAD Studio IDE contains its own integrated reflection tool, in the form of the meta data explorer tool that appears when you open a .NET assembly.

Global Assembly Cache

In COM, components can be deployed anywhere on the user's machine. Usually, a component's installation script records its location in the system registry. Command-line tools such as regsvr32 and tregsvr can also add and remove COM components from the registry. Registration of components is required in COM programming, even if the components are not intended to be shared by multiple applications. 

The .NET programming model drastically simplifies deployment of applications and components. On the .NET platform, non-shared components are deployed directly into the application's local installation directory; no registration is required. Alternatively, a non-shared component can be deployed in a directory specified in the application's configuration file. Again, registration is not required for this deployment scenario.  

Shared components are installed into a special location called the Global Assembly Cache (GAC). The GAC is an evolution of the system registry (though it is a completely separate mechanism and is not associated with the registry at all). The GAC exists in the file system in a folder called \Windows\Assembly. The .NET Framework supports simultaneous, or "side-by-side" deployment of different versions of the same component. When you view the Global Assembly Cache folder using Windows Explorer, you are actually looking at the GAC through a special shell extension. The shell extension presents all of the assemblies that have been installed into the GAC, with their version, culture, and public key information. 

There are three ways to install a .NET component into the GAC. The first way is to use the Framework SDK command-line tool called gacutil, which is discussed below. Another way is to install a component into the GAC is to navigate to the \Windows\Assembly folder using Windows Explorer, and then simply drag and drop the assembly into the directory listing pane. Finally, you can also use the .NET Configuration management tool, which is accessible through the Windows Control Panel.

Strong Names

The concept of a strong name is similar to that of the 128-bit Globally Unique Identifier (GUID) in COM programming. A GUID is a name that is guaranteed to be globally unique. Every .NET assembly has a basic name, which consists of a text string, a version number, and optional culture information. For shared assemblies installed into the GAC, the basic name alone is not enough to guarantee the assembly is uniquely identified. To generate a globally unique name, an encryption key with public and private components is used to generate a digital signature. The signature is then applied to the assembly using the .NET Framework SDK Assembly Linker (al.exe), or by using assembly attributes in source code.

Runtime Callable Wrappers and COM Callable Wrappers

Accessing a component, be it a .NET component or a COM server, is largely transparent. That is, when you are using a COM server in a .NET application, the COM server looks like any other .NET component. Similarly a .NET component, when exposed to an unmanaged application through COM Interop, looks like a COM server. This transparency is accomplished by behind-the-scenes proxies, or wrapper objects. 

When you use a COM object in a managed application, the Common Language Runtime (CLR) creates an RCW, which is the interface between managed and unmanaged code. The complexities of data marshaling and reference counting are handled by the RCW. In fact the RCW does not even expose the IUnknown and IDispatch interfaces.  

When you use a .NET component in an unmanaged application, the system creates a stand-in called a COM Callable Wrapper (CCW).

Primary Interop Assembly

In the COM programming model, once a GUID is assigned to a type, the GUID always refers to that specific type no matter where the type appears. For example, a common interface might be defined in many different type libraries, but each separate type library would have to define the interface with the same GUID, so the duplication is not a problem. However, if you generate COM Interop assemblies for these separate type libraries, a new and distinct assembly would be created for each type library. Each of these separate assemblies would contain distinct types (as far as the CLR is concerned). The strong identity and self-describing nature of .NET assemblies is actually working against you in this case. Here, it is leading to a GAC that is cluttered with interop assemblies that all contain RCWs for the same type library. Worse yet, to the CLR each assembly contains distinct and incompatible types, because each one has a different strong name. 

To avoid this proliferation of assemblies and potential type incompatibilities, the framework gives you the ability to designate one assembly as the primary interop assembly for a type library. A primary interop assembly is always signed with a strong name, by the original publisher of the type library.

Some of the functionality provided by the .NET Framework SDK tools is exposed in the development environment. This section is not intended to be a complete reference for these tools; it is merely a starting point for more exploration of the .NET Framework SDK, and hopefully will give you a bit more understanding of how to work with COM Interop technology in the IDE.

Importing and Exporting Type Libraries

Tlbimp is a command-line tool that you can use to generate a .NET assembly from a type library. Tlbimp will operate on a type library directly, or on an unmanaged DLL that contains a type library as an embedded resource. Note the assembly produced by tlbimp contains code for only the RCW, not for the original COM object itself. Therefore you must still deploy and register the COM object on the end-user's machine. The assembly also contains the types described in the type library, expressed as metadata. Tlbimp uses a command line switch to produce a primary interop assembly. 

The .NET Framework SDK contains another command-line tool called tlbexp that is used to create a type library from a .NET assembly. Such an exported type library would then be used to expose the .NET component as a COM server, for use within an unmanaged application.

Importing ActiveX Control Libraries

Aximp is a command-line tool that generates an ActiveX Control wrapper assembly. This assembly is required so that the ActiveX Control can be used on a Windows Form. A special utility is required, because a Windows Form can only host controls that are derived from the System.Windows.Forms.Control class, and the tlbimp utility does not create a wrapper derived from that class.  

The aximp tool will generate both interop assemblies (as with tlbimp, this includes dependent assemblies), and the ActiveX wrapper assembly. Like tlbimp, aximp has command-line switches to sign the assemblies produced with a strong name. Unlike tlbimp, aximp cannot generate a primary interop assembly.

Generating Strong Names

If you are deploying a .NET component into the GAC, you will need to sign your assembly with a strong name key. This is done by using a .NET Framework SDK command-line tool called sn. The assembly is signed with the strong name in one of three ways:

  • By specifying the strong name key file in the assembly linker (al) command line
  • By tagging the assembly with the AssemblyKeyFile attribute
  • By using a technique called "delay signing"
When using delay signing, the assembly is signed with the public portion of the key file at build time. Before shipping the assembly, the sn tool is used again to sign the assembly with the private key.

Deploying a .NET Component to the Global Assembly Cache

The .NET Framework SDK utility called gacutil is a command-line program that is used to install, remove, and view components in the GAC. The gacutil command is usable from installation scripts as well as from batch files. The gacutil command supports installation and removal of shared assemblies, with and without the use of reference counting. It is recommended that the non-reference counted command switches be used only during development. Installation scripts that use gacutil to install shared components should always use the reference counted command line switches.

All of the functionality encompassed by the .NET Framework SDK command-line tools is in fact exposed by the .NET Framework Class Library itself. The RAD Studio IDE also takes advantage of the .NET Framework classes to expose interoperability features. The IDE goes beyond the capabilities of the command-line tools, however, making interoperation with unmanaged components even easier.

Type Libraries and Interop Assemblies

The IDE initiates the creation of interop assemblies through the Project Manager. When you add a reference to a DLL to your project, you can select from registered type libraries and unmanaged DLLs, or you can browse to an unregistered component.  

The IDE creates one interop assembly for each imported type library or DLL. The assemblies are named Interop.LibraryName.dll, where LibraryName is the name of the type library. The name of the library is specified in the library statement in IDL source code, so the file name of the generated assembly might be different from that of the original DLL or type library. Each interop assembly (and all of its dependent assemblies) are added to your project as referenced assemblies. The types contained in the interop assembly are added to a namespace with the same name as the type library; Again, this is derived from the library statement in IDL source code. 

If the assembly you reference has a primary interop assembly, the IDE will recognize this and avoid generating a new interop assembly. In this case, the IDE will add a reference to the primary interop assembly in the GAC, and it will not copy the assembly to your local project directory.

Importing ActiveX Controls

To use an ActiveX Control in your managed application, you must first add the control to the tool palette. This will create both an interop assembly, and an ActiveX assembly with a wrapper class derived from System.Windows.Forms.AxHost. The ActiveX wrapper assembly will be named AxInterop.LibraryName.dll, where LibraryName is the name of the type library. Dragging the control from the palette onto a Windows Form will automatically add references to both assemblies to your project. 

Once on your form, the ActiveX Control can be treated as any other .NET component. You can select the control, and set its properties and event handlers in the Object Inspector. The ActiveX Control wrapper will expose the properties of the Windows.Forms.Control class, while properties exposed by the ActiveX Control will be grouped under the Misc category.

Interop Assemblies and the Project Manager

Interop assemblies (including ActiveX Control wrapper assemblies) generated by the IDE are kept in a separate folder called COMImports, underneath your project. Each generated assembly will have its 'Copy Local' property set, meaning that when the project is built, the assembly will be copied to the folder where the final build target of the project is kept. The exceptions to this rule are primary interop assemblies, which are deployed in the GAC. When you add a reference to a primary interop assembly, the IDE will not copy the assembly to the COMImports folder. The assembly will still be shown in the Project Manager, however, if you right click on it to display its properties, you will notice that the 'Copy Local' setting is turned off. 

The list of referenced assemblies (including those that are not interop assemblies) is an attribute of your project. If the COMImports folder (or one of the interop assemblies contained therein) does not exist when you open a project, the IDE will attempt to recreate it. If the IDE cannot create an interop assembly, it will still be shown as a referenced assembly in the Project Manager; the IDE will highlight such an assembly so that you know it currently does not exist (or is not registered) on the machine.

Copyright(C) 2008 CodeGear(TM). All Rights Reserved.
What do you think about this topic? Send feedback!