24.7 Using .NET Objects in COM

 <  Day Day Up  >  

You want to expose a .NET object to an unmanaged client that uses COM .


Technique

Creating a .NET object to be used with a COM client is a little more involved than using a COM object within a .NET project. The reasoning is explained later in this recipe. The first step is to create the .NET object. However, the architecture of the .NET object must use interfaces because COM is an interface-based language and expects to interact with other objects through defined interfaces. This example defines three interfaces, which use inheritance to go from a general IVehicle interface to the more concrete IAirplane . Each interface contains a single property for simplicity. In addition to a class that implements the IAirplane interface, create a class that does not implement any interfaces but rather contains a single public method. The final C# code should appear similar to Listing 24.2.

Listing 24.2 Interface Declarations for a .NET Object
 using System; namespace DotNetObject {     public interface IVehicle     {         int Wheels         {             get;             set;         }     }     public interface IAirVehicle :  IVehicle     {         int Elevation         {             get;             set;         }     }     public interface IAirplane : IAirVehicle     {         int Capacity         {             get;             set;         }     }     public class Airplane : IAirplane     {         private int capacity, elevation, wheels;         public Airplane()         {             capacity = 200;             elevation = 0;             wheels = 4;         }         public int Capacity         {             get             {                 return this.capacity;             }             set             {                 this.capacity = value;             }         }         public int Elevation         {             get             {                 return this.elevation;             }             set             {                 this.elevation = value;             }         }         public int Wheels         {             get             {                 return this.wheels;             }             set             {                 this.wheels = value;             }         }     }     public class NonInterfaceClass     {         public void Test()         {             Console.WriteLine("NonInterfaceClass.Test succeeded");         }     } } 

The first step to prepare your assembly for COM interop is to generate a strong key . COM uses location information within the Registry to locate the library a COM object exists in. However, when you generate the type library shown later, all that is generated is the type library without an associated COM DLL. The COM Callable Wrapper (CCW) that is created when your COM object attempts to instantiate a .NET object is contained within the CLR. Therefore, because the COM object's first line of communication is with the CLR, the location information points to the Microsoft Common Object Runtime Execution Engine ( mscoree.dll ). What you need to do is tell the mscoree.dll where your assembly is located, which you do by assigning a strong key to your assembly before you export the type library (which this example demonstrates ) or install the assembly into the Global Assembly Cache (GAC).

To generate a strong key name , which you need to do only once, open a command-prompt window and navigate to your .NET project directory. Next, run the strong-key “generation program specifying the name of the key file to create:

 
 sn.exe k strongKey.skn 

This line assumes that the Visual Studio .NET SDK is in your PATH environment variable. If not, you can open a command prompt using the link in the Tools folder of your Visual Studio .NET 2003 Start menu program group .

Once you generate a strong key, you have to sign your assembly. The C# compiler does this step each time you build. To sign the assembly, open the AssemblyInfo.cs file and change the following line:

 
 [assembly: AssemblyKeyFile("")] 

You add the location of the key file. This location is relative to the target directory where your final assembly is placed, so you have to use a relative path if your strong key file is within your main source directory:

 
 [assembly: AssemblyKeyFile("../../strongKey.snk")] 

The last step is similar to the step you used when generating an interop assembly for your .NET object in Recipe 24.2, "Using COM Objects in .NET." Additionally, we make this step automatic so you don't have to repeat the process each time you build your .NET project. Click on Project, Properties from the main menu and select the Common Properties, Build Events property pages. In the post-build event command line, enter RegAsm.exe as the program to execute, passing as arguments the location of your .NET assembly, the location of the resulting type library prefaced with the tlb: switch, and the / codebase switch:

 
 "C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322/RegAsm.exe " "$(TargetPath)"     /tlb:"$(TargetDir)$(TargetName).tlb" /codebase 

In this command, $(TargetPath) is the full path to the .NET assembly and $(TargetDir)\$(TargetName).tlb creates a type library in the same location as the assembly and with the same name but using a .tlb file extension. The last argument, /codebase , helps mscoree.dll find your assembly when a COM object requests it. In keeping with COM tradition, the codebase argument places the full path to your assembly within the Windows Registry.

At this point, you now have a .NET assembly containing objects that a COM client can use. The last step to complete this exercise is to create the COM client. Create a new Visual C++ project using the Win32 Console Application template. When the project opens, you see only the required parts for a full C++ application, which isn't much. The first step is to tell the compiler that you are going to use an object contained within another type library. You use the #import keyword. This keyword is somewhat similar to the using keyword in C#, although internally different in many ways. Next, create a method called Test that will be used to test the .NET objects, as shown in the code following this paragraph. The first line is a variable declaration using #import -generated smart pointers. Explaining these smart pointers is well beyond the scope of this book. In general, you are creating an interface pointer whose implementation is contained within the class identified by the CLSID parameter in the constructor. These two objects map to the IAirplane interface and Airplane class, respectively, within your .NET class. At this point, the object is created and ready for use. The last two lines simply set the Capacity property and then print out that property to the console:

 
 #include "stdafx.h" #import "../7_NetClient/bin/Debug/DotNetObject.tlb" named_guids void Test() {     DotNetObject::IAirplanePtr pAirplane(DotNetObject::CLSID_Airplane);     pAirplane->Capacity = 500;     printf("Airplane Capacity: %d\n", pAirplane->Capacity); } int _tmain(int argc, _TCHAR* argv[]) {     CoInitialize(NULL);     Test();     CoUninitialize();     return 0; } 

Comments

As mentioned right at the beginning of this recipe, making a .NET object COM enabled is a lot more involved than the other way around. Even though the process is relatively trivial from the .NET standpoint, you still have to rearchitect your application to make extensive use of interfaces if you haven't already done so. Because COM only uses interface methods when accessing an object, you must implement interfaces on your .NET object if you ever want a COM client to have access. In most cases, you can simply take a .NET class and create interfaces based off existing class members .

When your .NET object is instantiated by a COM object, the CLR creates a CCW, which behaves similarly to the RCW going in the other direction. Calls from the COM client are handled in the proxy object contained within the CLR, and any necessary parameters are marshaled into their equivalent .NET data types before the final call is made on the .NET object.

One of the main reasons that this method is a little more involved than calling a COM object from .NET concerns something known as LocalServer32 . When a COM object is registered, its location is placed within a unique identifying number known as a CLSID, which is a globally unique identifier (GUID). Within this key is a subkey called LocalServer32 . In most cases, this value is a location to the binary file housing the COM object. However, when using COM interop, the location is pointing to mscoree.dll , thereby losing any information regarding the location of the assembly where the object is located. To solve this problem, you have to tell the CLR where the associated assembly is located, either by using the /codebase argument for the RegAsm tool or by installing the assembly into the GAC .

 <  Day Day Up  >  


Microsoft Visual C# .Net 2003
Microsoft Visual C *. NET 2003 development skills Daquan
ISBN: 7508427505
EAN: 2147483647
Year: 2003
Pages: 440

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net