Primary Interop Assemblies (PIAs)
Summary: This document explains the process of creating metadata that describes a set of existing COM types to interoperate with the common language runtime. (7 printed pages)
Metadata and the Common Language Runtime
General Questions about PIAs
What is a PIA?
Why are PIAs important?
How do I make a PIA for my COM type library?
What if I don't use a PIA?
How can I customize my PIA?
How are PIAs handled differently by the common language runtime and Visual Studio?
Can I combine multiple PIAs in one file?
What namespace should I use for my PIA?
What file name should I use for my PIA?
Where do I get the key to sign my PIA?
How do I distribute my PIA to developers?
Can I change or revise my PIA after I ship it?
Can I create publisher policy for a PIA?
Are there situations when it would be OK not to use a PIA?
Where do I get PIAs for Microsoft and non-Microsoft products?
In order to interoperate with existing COM types, the common language runtime requires a description of those types in a format that it can understand. The form of type information understood by the common language runtime is called metadata, and is contained within a managed assembly. Therefore, before an application can interoperate with COM types, it requires a metadata description of the type being used.
This document explains the process of creating metadata that describes a set of existing COM types. The metadata is essentially the run time equivalent of a COM type library. Since metadata is required to use existing COM types, developers who have published COM type libraries should be particularly interested in how to create metadata for their COM Libraries.
Like any other managed assembly, an interop assembly is a collection of types that are deployed, versioned, and configured as a single unit. However, unlike other managed assemblies, an interop assembly contains type definitions (not implementation) of types that have already been defined in COM. These type definitions allow managed applications to bind to the COM types at compile time and provide information to the common language runtime about how the types should be marshaled at run time.
While any number of interop assemblies may exist that describe a given COM type, only one interop assembly is labeled the PIA. The PIA contains the official description of the types as defined by the publisher of those types. The PIA may contain certain customizations that make the types easier to use from managed code. The PIA is always signed by the publisher of the original COM type.
Any interop assembly that is not provided by the publisher of the COM types is considered unofficial and should be avoided. Because the types defined in such an assembly are not to be signed by the publisher of the PIA, they are incompatible with the definitions provided in the PIA.
PIAs are important because they provide unique type identity. The PIA distinguishes the official type definitions from counterfeit definitions provided by other interop assemblies. Having a single type identity ensures type compatibility between applications that share the types defined in the PIA. Because the PIA is signed by its publisher and labeled with the PrimaryInteropAssembly attribute, it can be differentiated from other interop assemblies that define the same types.
In most cases, creating a PIA is easy. The Tlbimp (Type Library Importer) tool provided in the Microsoft® .NET Framework SDK is capable of creating an interop assembly from an existing type library. The /primary switch on Tlbimp is used to create a PIA. All PIAs must be signed by their publisher so the publisher key must also be supplied with the /keyfile switch. For example:
TlbImp Widget.tlb /primary /keyfile:AcmeKey.snk /out:WidgetLib.dll
In the example above, Widget.tlb is the type library being imported, AcmeKey.snk is the keyfile containing the public key for the Acme Company, and WidgetLib.dll is the PIA that will be generated. A test keyfile can be generated with SN utility (SN.EXE —k AcmeKey.snk). Before the assembly is actually deployed, it should be signed with the company's real key. The same key should be used to sign all assemblies from a single company.
PIAs can only reference other PIAs. You should therefore generate PIAs for any dependent assemblies before generating a PIA for the parent assembly. In some cases, you may need to obtain a PIA from another company in order to produce a PIA for your assembly. After you have all dependent assemblies, you can add references to them with the /reference switch on Tlbimp.
TlbImp WidgetUtil.tlb /primary /keyfile:AcmeKey.snk /out:WidgetUtil.dll TlbImp Widget.tlb /primary /keyfile:AcmeKey.snk /reference:WidgetUtil.dll /out:WidgetLib.dll
In most cases, interop assemblies are produced by the Tlbimp tool. Occasionally, interop assemblies are generated directly from managed source code (such as C#). Interop assemblies generated from source code can be PIAs by adding the PrimaryInteropAssembly attribute and the Guid attribute to the assembly. For example:
// assign the assembly the Guid o the type library [assembly:Guid("97d25db0-0363-1cf-abc4-02608 c9e7553"] // indicate that this assembly is the PIA for version 4.2 of the tlb [assembly:PrimaryInteropAssembly(4, 2)]
Not using a PIA will lead to type incompatibilities for customers. For example, Company A uses the COM Widget object provided by the Acme Company within its managed application. Company A decides to produce another interop assembly with definitions of Acme's Widget library rather than use the PIA provided by the Acme Company.
Company B obtains the PIA for the Widget Library directly from the Acme Company and uses it within its managed application. Both applications work fine on their own until a developer from one of Company A and Company B's common customers calls to find out why he keeps getting a type mismatch whenever he tries to pass a Widget from Company A to Company B.
The reason for the type mismatch is because the Widget type used by Company A is completely different from the Widget type used by Company B. Even though they both have the same name, each interop assembly was signed by a different publisher, thereby making the types incompatible. The simple solution is for both companies to use the PIA provided by the Acme Company.
There are situations when a publisher may want to customize its PIA. For example, the publisher may want to:
- Rename or hide certain elements in the assembly.
- Add attributes to change marshaling behavior.
In the version 1.0 release, there are two ways to create a custom PIA:
- Create the type definitions for the PIA in source code using a managed language and a variety of custom attributes.
- Use Tlbimp to produce a PIA, which is then customized by modifying the Microsoft intermediate language (MSIL) type definitions produced by Tlbimp.
The only tools for making such modifications are ILDASM and ILASM. The process includes the following steps:
- Import the type library with Tlbimp as usual.
TlbImp MyLib.tlb /primary /out:MyLib.dll
- Use ILDasm to generate MSIL source code for the assembly.
ILDASM MyLib.dll /out=MyLib.il
- Modify the MSIL code with your favorite text editor as necessary.
- Recompile the MSIL using the ILASM compiler.
ILAsm /DLL /KEY=MyKey.snk MyLib.il
Step 2 above may also produce a RES file if resources are found in the assembly. If a RES file is generated, it should be compiled back into the assembly in step 4 using the /resource option on ILASM.
To ensure that PIAs are used as often as possible, the common language runtime and Microsoft® Visual Studio® handle PIAs a bit differently than other assemblies.
At registration: PIAs should be registered with the RegAsm tool. RegAsm will automatically create registry entries under each type library key for which the assembly being registered is the PIA. For example, if a specific assembly is a PIA for versions 1.0, 1.5, and 2.0 of the ADODB type library, an entry will be created under each type library key identifying the registered assembly as the PIA for that type library.
In Visual Studio: When a user attempts to add a reference to a type library that has a registered PIA, Visual Studio will silently use the registered PIA instead of reimporting the type library with Tlbimp. This ensures that the PIA is used whenever possible.
If the user attempts to add a reference to a type library that does not have a registered PIA, the user is warned that a PIA does not exist, is referred to the MSDN site to obtain the PIA, and is offered the opportunity for Visual Studio to import the type library at that time. By having Visual Studio import the type library, the user bypasses the PIA and any customizations the PIA author may have made. This is not advisable.
Tlbimp does not provide a way of combining multiple assemblies into a single PIA; however, it is possible to combine assemblies by merging the MSIL generated by ILDASM with the MSIL from other assemblies. Using this approach requires a basic understand of MSIL.
The namespace of the PIA is defined at import time. By default, the namespace is the same as the type library name (the name within the type library, not the type library file name). The namespace can be changed with the /namespace option on Tlbimp.
Deciding on the correct namespace can sometimes be confusing. Here are some things to consider.
- Existing Microsoft® Visual Basic® 6.0 code will scope the types by their type library name. For example, the ADO library is referred to as ADODB in Visual Basic 6.0. Using the default namespace will provide source compatibility for Visual Basic 6.0 applications because the default namespace will be the same as the original type library name (ADODB).
- You may want to reserve the "real" namespace for a managed version of the library that will be available in the future. For example, the Microsoft Excel team may decide to use the namespace "Office.Excel.Interop" so that the more familiar namespace "Office.Excel" can be used for a managed abstraction layer that could be built in the future.
- Users of your PIA will have to refer to the namespace in source code, so the namespace should be descriptive but brief.
- Avoid using abbreviations.
There are three important names associated with any assembly: the assembly name, the namespace name, and the file name of the assembly. By default, Tlbimp creates assemblies with all three names that are the same as the name of the type library the assembly was imported from (that is, the library name, not the name of the file containing the library). For example, if the FooLib type library contained in the foo.dll was imported with Tlbimp, an assembly named FooLib would be created with the namespace called FooLib in a file called FooLib.dll.
The assembly name and the file name created by Tlbimp can be altered with the /out option. The namespace can be altered with the /namespace option.
Where the imported type library has the same name as the file containing the library, Tlbimp will not overwrite the input file with the output file. In such cases the user must specify a different output file name using the /out option. A common naming convention used in this case is to append ".Interop" to the name of the library. For example, the type library name Foo in the file named foo.dll would be imported to Foo.Interop.dll.
PIAs can be signed with the same public/private key pair used to sign other managed assemblies. A key can be produced with the SN utility that is part of the Microsoft® .NET Framework SDK.
PIAs should be distributed as part of any unmanaged code library that a vendor produces. The PIA should be distributed in much the same way as a type library would be distributed with a DLL or EXE.
Yes, but the assembly version number should be changed with each new version produced.
Yes, publisher policy, application policy, and machine policy can all be used to manage how applications bind to PIAs.
In situations where a PIA is not available, you may need to use an interop assembly that is not a PIA, which is known as an Alternate Interop Assembly. If you can't use a PIA assembly, you should avoid exposing the types defined in the Alternate Interop Assembly from your assembly. Using an Alternate Interop Assembly is acceptable only if the types defined in the assembly are limited to internal use. Your assembly cannot publicly expose types from an Alternate Interop Assembly. In other words, your assembly cannot contain public methods, fields, or properties that use the types defined in the Alternate Interop Assembly. Your assembly also cannot implement any interfaces defined in an Alternate Interop Assembly.
Some PIAs will ship with Microsoft Visual Studio .NET.