Primary Interop Assemblies

Team-Fly    

 
.NET and COM Interoperability Handbook, The
By Alan Gordon
Table of Contents
Chapter Seven.  Advanced .NET to COM Interop

Primary Interop Assemblies

There is a major complication associated with generating an Interop assembly for a COM component. The problem occurs when you attempt to import a type library that references other type libraries. A type library (type library A) references another type library (type library B) when the COM component associated with type library A uses types from type library B in its interface. For instance, I may want to define an upgraded version of my financial COM component that can also issue stock quotes. To provide this functionality, I could implement the IStockMonitor interface that I defined in the Stock Server earlier in this chapter. The GetPrice method in this interface will allow me to return stock quotes, and I could reuse the existing implementation in the StockServer through aggregation as shown in Figure 7-7.

Figure 7-7. An enhanced version of the financial component.

graphics/07fig07.gif

The following code shows the IDL for my new financial component:

 1.  [ 2.    uuid(EB47FA01-22EE-11D3-998A-E0EC08C10000), 3.    version(1.0), 4.    helpstring("financialcomponent 1.0 Type Library") 5.  ] 6.  library FINANCIALCOMPONENTLib 7.  { 8.    importlib("stdole32.tlb"); 9.    importlib("stdole2.tlb"); 10.  importlib("stockserver.tlb");  11.   [ 12.   object, 13.   uuid(EB47FA0D-22EE-11D3-998A-E0EC08C10000), 14.   dual, 15.   helpstring("ITimeValue Interface"), 16.   pointer_default(unique) 17.   ] 18.   interface ITimeValue : IDispatch 19.   { 20.     [id(1)] HRESULT MonthlyPayment(21.       [in] short numMonths, 22.       [in] double interestRate, 23.       [in] double loanAmt, 24.       [out,retval] double *result); 25.     [id(2)] HRESULT LoanAmount(26.       [in] short numMonths, 27.       [in] double interestRate, 28.       [in] double monthlyPmt, 29.       [out,retval] double *result); 30.   }; 31. 32.   [ 33.     object, 34.     uuid(1C208680-22F2-11d3-998A-E0EC08C10000), 35.     dual, 36.     helpstring("ITaxCalculator Interface"), 37.     pointer_default(unique) 38.   ] 39.   interface ITaxCalculator : IDispatch 40.   { 41.     [id(1)] HRESULT CalculateTax(42.       [in] double earnings, 43.       [out,retval] double *tax); 44.   }; 45. 46.   [ 47.     uuid(EB47FA0E-22EE-11D3-998A-E0EC08C10000), 48.     helpstring("Financial Class") 49.   ] 50.   coclass CFinancial 51.   { 52.     [default] interface ITimeValue; 53.     interface ITaxCalculator; 54.  interface IStockMonitor;  55.   }; 56. }; 

Notice that, on line 10, I added a importlib statement for the stockserver's type library. This indicates that the financial component type library depends on types in the stockserver's type library. You can see the actual dependency on line 54 where I added the IStockMonitor interface to the list of interfaces that the CFinancial class implements.

When you use a COM component through Interop, the managed code client will need to have an Interop assembly for each COM component that the COM component you want to use depends on. Therefore, in my example, if I am using the enhanced Financial component from a managed client, I will need Interop assemblies for both the financial component and the stock server.

The usual behavior of the Type Library Importer (tlbimp.exe) and the Add reference command in Visual Studio .NET is to generate a separate Interop assembly for each type library that the specified type library references. Therefore, if you run tlbimp on the modified financial component as follows :

 tlbimp financialcomponent.dll /out:dotnetfinancial.dll 

The type library importer will actually generate two assemblies: (1) an Interop assembly called dotnetfinancial.dll that contains managed equivalents of all the types declared in the financial component and (2) an interop assembly called StockServerLib.dll that contains the managed equivalents of the types in the stock server component. I did not have to explicitly tell tlbimp to generate this second assembly. It looked at the reference that the financial component's type library had to the stock server component and realized that it had to generate managed code equivalents for the types in the stock server type library in order for the financial component to be usable. When you build a managed code client that uses the financial component, you must reference both Interop assemblies in order to use it. If you reference only the financial component assembly, you will get the following compiler error:

 Referenced class 'FINANCIALCOMPONENTLib.CFinancialClass' has base class or interface 'STOCKSERVERLib.IStockMonitor' defined in an assembly that is not referenced.  You must add a reference to assembly 'STOCKSERVERLib'. 

Unfortunately, the need to reference multiple assemblies is only one of the problems you will encounter if you attempt to use a COM component that is dependent on other COM components . Imagine that you had a second component (perhaps a business object that is used for calculating the value of investments) that also references the stock server; let's call it investmentcomponent.dll. If you run tlbimp on this component, it will generate two assemblies: (1) an interop assembly for investmentcomponent.dll and (2) an Interop assembly for the stock server. If you run tlbimp.exe on the investment component and the financial component, it will generate two different versions of the Stock Server Interop assembly as shown in Figure 7-8.

Figure 7-8. Two versions of the stock server Interop assembly.

graphics/07fig08.gif

This causes major problems because COM components have a completely different notion of identity than .NET components. Whether it's a class or an interface or a type library, a COM type has a unique identifier (a GUID) that identifies it. With .NET, a type's identity is assembly relative. Therefore, even if two types (two classes, for instance) have the same name and are structurally identical (that is, they have exactly the same members ), they are considered to be two different types by the CLR if they reside in different assemblies. As long as you don't need to write a client application that uses both the enhanced financial component and the investment component, you're okay. However, as soon as you attempt to use both assemblies in the same application, you will have difficulties because each of these assemblies is dependent on its own version of an interop assembly for the stock server. Not only will you have the clutter associated with having multiple assemblies that contain structurally identical types with the same name, you also may have type mismatch errors in your code. The solution to this problem is Primary Interop Assemblies (PIAs).

A PIA is a digitally signed assembly that is installed in the GAC and contains a special attribute that identifies it as a PIA. When a type library references another type library, the Type Library Importer checks for a PIA before it generates a new Interop assembly for the referenced type library. If it finds a PIA, it does not generate an Interop assembly for the referenced type library. It assumes that all clients will share the PIA registered in the GAC.

To create a PIA, you must use tlbimp with the /primary parameter. A PIA must have a strong name, so you must use the / publickey , keyfile , or /keycontainer parameter on tlbimp to specify a strong name for the resulting assembly. You cannot use the Add Reference command in Visual Studio .NET to generate a PIA. The following command will create a PIA for the Stock Server component:

 tlbimp stockserver.exe /primary /keyfile:mykey.snk /out:stockserver.dll 

The type library importer searches for PIAs in the registry. When you generate an Interop Assembly, the Type Library Importer will look in the type library's entry in the registry for special entries that indicate that a PIA exists for the type library. You must add these registry entries before an Interop assembly will function as a PIA. It's easy to do this using the Assembly Registration Tool (regasm), which is a part of the .NET Framework SDK. Simply run the following command:

 regasm stockserver.dll 

Regasm will add the following value to the registry.

 HKEY_CLASSES_ROOT\TypeLib\{LIBID}\Major.Minor\PrimaryInteropAssemblyName 

Where {LIBID} is the GUID associated with the type library and Major.Minor is the major and minor version numbers of the assembly. The data at the PrimaryInteropAssemblyName value is just the full name of the PIA, as in the following example:

 STOCKSERVERLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=8a707be49fd7d8f4 

If you do not want to install your PIA into the GAC, you can instead use regasm with the / codebase parameter and specify a path where the type library importer should search for the PIA. If you do this, regasm will add a second registry value at the following location.

 HKEY_CLASSES_ROOT\TypeLib\{LIBID}\Major.Minor\PrimaryInteropAssemblyCodeBase 

The data at this value will just be the path to the primary interop assembly, such as the following:

 file:///C:/Alan/books/dotnetbook/demos/chapter7/stockserver/server/stockserver.dll 

After you have registered a PIA for a COM type library, the Type Library Importer will not generate a separate interop assembly for dependent type libraries. In other words, referring back to my example where the enhanced version of the financial COM component referenced the stock server COM component, if there is no PIA for the stock server, the Type Library Importer will generate two interop assemblies when I run it against the financial component: one for the financial component and one for the stock server component. If I register a PIA for the stock server component, the Type Library Importer will produce only one interop assembly, which is the one for the financial component. The generated interop assembly will reference the PIA.

If you are a vendor who produces COM components, you should create PIAs for your COM components. Not only will you save your customers a lot of grief with redundant Interop assemblies, but, in some cases, you may need to make small modifications to the Interop assembly that tlbimp generates for your COM component to make it easy to use from a COM client. By making these modifications in a PIA, you save your customers the grief of having to make these modifications themselves .


Team-Fly    
Top
 


. Net and COM Interoperability Handbook
The .NET and COM Interoperability Handbook (Integrated .Net)
ISBN: 013046130X
EAN: 2147483647
Year: 2002
Pages: 119
Authors: Alan Gordon

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