标签:purpose variables resolve instr define external nan eva nes
1.1 After publishing Programmatically Register COM Dlls in C# back in 2011, I received a number of requests for advise on how to register managed assemblies as COM servers.
1.2 In this blog, I will present the reader with a sample C# program that will perform such registration.
1.3 The sample assembly COM-registration source codes can be used to target 32-bit and 64-bit assemblies.
1.4 I will also provide 2 sets of programs (one 32-bit and the other 64-bit) for testing.
2.1 The full source codes for this article can be found here in CodePlex.
2.1 Shown below is a partial listing of the source codes for the program :
// Program.cs // Main control source codes. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Configuration; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using System.Collections; using System.Collections.Specialized; using System.Reflection; using System.IO; namespace RegisterAssembly { // The managed definition of the ICreateTypeLib interface. [ComImport()] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [Guid("00020406-0000-0000-C000-000000000046")] public interface ICreateTypeLib { IntPtr CreateTypeInfo(string szName, System.Runtime.InteropServices.ComTypes.TYPEKIND tkind); void SetName(string szName); void SetVersion(short wMajorVerNum, short wMinorVerNum); void SetGuid(ref Guid guid); void SetDocString(string szDoc); void SetHelpFileName(string szHelpFileName); void SetHelpContext(int dwHelpContext); void SetLcid(int lcid); void SetLibFlags(uint uLibFlags); void SaveAllChanges(); } partial class Program { // Command line options. const string TYPELIB = "TYPELIB"; const string CODE_BASE = "CODE_BASE"; const string VERBOSE = "VERBOSE"; const string UNREGISTER = "UNREGISTER"; // Windows API to register a COM type library. [DllImport("Oleaut32.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)] public extern static UInt32 RegisterTypeLib(ITypeLib tlib, string szFullPath, string szHelpDir); [DllImport("Oleaut32.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)] public extern static UInt32 UnRegisterTypeLib(ref Guid libID, UInt16 wVerMajor, UInt16 wVerMinor, int lcid, System.Runtime.InteropServices.ComTypes.SYSKIND syskind); public static bool Is32Bits() { if (IntPtr.Size == 4) { // 32-bit return true; } return false; } public static bool Is64Bits() { if (IntPtr.Size == 8) { // 64-bit return true; } return false; } static void DisplayUsage() { Console.WriteLine("Usage :"); Console.WriteLine("RegisterAssembly [CODE_BASE] [CREATE_TYPELIB] [VERBOSE] [UNREGISTER] <path to managed assembly file>"); Console.WriteLine("where CODE_BASE, CREATE_TYPELIB, VERBOSE, UNREGISTER are optional parameters"); Console.WriteLine("Note that the UNREGISTER parameter cannot be used with the CODE_BASE or the CREATE_TYPELIB parameters."); Console.WriteLine(); } static bool ProcessArguments(string[] args) { const int iCountMinimumParametersRequired = 1; if (args == null) { Console.WriteLine(string.Format("Invalid number of parameters.")); return false; } if (args.Length < iCountMinimumParametersRequired) { Console.WriteLine(string.Format("Invalid number of parameters (minimum parameters : [{0:D}]).", iCountMinimumParametersRequired)); return false; } for (int i = 0; i < args.Length; i++) { switch (args[i].Trim()) { case TYPELIB: { m_bTypeLib = true; break; } case CODE_BASE: { m_bCodeBase = true; break; } case VERBOSE: { m_bVerbose = true; break; } case UNREGISTER: { m_bUnregister = true; break; } default: { if (string.IsNullOrEmpty(m_strTargetAssemblyFilePath)) { m_strTargetAssemblyFilePath = args[i].Trim(); break; } else { Console.WriteLine(string.Format("Invalid parameter : [{0:S}].", args[i])); return false; } } } } if (m_bUnregister) { if (m_bCodeBase) { Console.WriteLine(string.Format("UNEGISTER flag cannot be used with the CODE_BASE flag.")); return false; } } return true; } static bool DoWork() { try { if (m_bVerbose) { Console.WriteLine(string.Format("Target Assembly File : [{0:S}].", m_strTargetAssemblyFilePath)); } if (m_bUnregister) { if (PerformAssemblyUnregistration(m_strTargetAssemblyFilePath) == false) { return false; } if (m_bTypeLib == true) { return PerformTypeLibUnRegistration(m_strTargetAssemblyFilePath); } else { return true; } } else { if (PerformAssemblyRegistration(m_strTargetAssemblyFilePath, m_bCodeBase) == false) { return false; } if (m_bTypeLib == true) { return PerformTypeLibCreationAndRegistration(m_strTargetAssemblyFilePath); } else { return true; } } } catch (Exception ex) { Console.WriteLine(string.Format("An exception occurred. Exception description : [{0:S}].", ex.Message)); return false; } } static void Main(string[] args) { if (ProcessArguments(args) == false) { DisplayUsage(); return; } if (ReadConfigSettings() == false) { return; } DoWork(); } public static bool m_bVerbose = false; private static bool m_bTypeLib = false; private static bool m_bCodeBase = false; private static bool m_bUnregister = false; private static string m_strTargetAssemblyFilePath = null; private static ITypeLibExporterNotifySink m_pITypeLibExporterNotifySink = null; } }
The sections that follow will provide more details on the running process of the program.
2.1 The program takes at minimum one parameter (i.e. the path to the assembly to be registered).
2.2 VERBOSE , UNREGISTER, CODE_BASE and TYPELIB are optional parameters which can be used in combination.
2.3 The VERBOSE option indicates to the program to display additional useful information at runtime.
2.4 Unless the UNREGISTER parameter is used, it is assumed that the program is to perform assembly un-registration.
2.3 The CODE_BASE parameter indicates that the assembly registration includes the assembly’s full path in the registry.
2.4 The presence of the TYPELIB argument indicates the following :
3.1 The source codes for the PerformAssemblyRegistration() method is found in AssemblyRegistration.cs :
static bool PerformAssemblyRegistration(string strTargetAssemblyFilePath, bool bCodeBase) { try { RegistrationServices registration_services = new RegistrationServices(); Assembly assembly = Assembly.LoadFrom(strTargetAssemblyFilePath); AssemblyRegistrationFlags flags; bool bRet = false; if (bCodeBase == true) { flags = AssemblyRegistrationFlags.SetCodeBase; } else { flags = AssemblyRegistrationFlags.None; } bRet = registration_services.RegisterAssembly(assembly, flags); if (bRet) { Console.WriteLine(string.Format("Successfully registered assembly [{0:S}].", strTargetAssemblyFilePath)); if (m_bVerbose) { Type[] types = registration_services.GetRegistrableTypesInAssembly(assembly); Console.WriteLine(string.Format("Types Registered :")); foreach (Type type in types) { Console.WriteLine(string.Format("GUID : [{0:S}] [{1:S}].", type.GUID.ToString(), type.FullName)); } } } else { Console.WriteLine(string.Format("Failed to register assembly [{0:S}].", strTargetAssemblyFilePath)); } return bRet; } catch (Exception ex) { Console.WriteLine(string.Format("An exception occurred. Exception description : [{0:S}].", ex.Message)); return false; } }
3.2 PerformAssemblyRegistration() takes 2 parameters :
The assembly registration work is done via the RegistrationServices class, specifically, the RegisterAssembly() method.
3.3 If all went well with the assembly registration process, the types which have been registered will be displayed if the “VERBOSE” flag was used.
3.4 If the registration process was successful, PerformAssemblyRegistration() will return true.
3.5 If the registration process failed, either because the RegisterAssembly() returned false or because an exception was thrown, PerformAssemblyRegistration() will return false and the program will stop.
4.1 The source codes for the PerformTypeLibCreationAndRegistration() method is also listed in AssemblyRegistration.cs :
static bool PerformTypeLibCreationAndRegistration(string strTargetAssemblyFilePath) { try { string strTargetAssemblyDirectory = Path.GetDirectoryName(strTargetAssemblyFilePath); string strTargetAssemblyFileNameWithoutExtension = Path.GetFileNameWithoutExtension(strTargetAssemblyFilePath); string strTargetTypeLibFullPath = strTargetAssemblyDirectory + "\\" + strTargetAssemblyFileNameWithoutExtension + ".tlb"; TypeLibConverter converter = new TypeLibConverter(); Assembly assembly = Assembly.LoadFrom(strTargetAssemblyFilePath); TypeLibExporterFlags flags; if (Is32Bits()) { flags = TypeLibExporterFlags.ExportAs32Bit; } else if (Is64Bits()) { flags = TypeLibExporterFlags.ExportAs64Bit; } else { Console.WriteLine(string.Format("Unknown bit-ness.")); return false; } ICreateTypeLib create_typeLib = (ICreateTypeLib)(converter.ConvertAssemblyToTypeLib (assembly, strTargetTypeLibFullPath, flags, m_pITypeLibExporterNotifySink)); // SaveAllChanges() will create the TypeLib physical file // based on strTargetTypeLibFullPath. create_typeLib.SaveAllChanges(); ITypeLib typelib = (ITypeLib)create_typeLib; UInt32 uiRetTemp = RegisterTypeLib(typelib, strTargetTypeLibFullPath, null); if (uiRetTemp == 0) { Console.WriteLine(string.Format("TypeLib File [{0:S}] registered.", strTargetTypeLibFullPath)); } else { Console.WriteLine(string.Format("Failed to register TypeLib File [{0:S}]. Error code : [{1:D}]", strTargetTypeLibFullPath, uiRetTemp)); return false; } return true; } catch (Exception ex) { Console.WriteLine(string.Format("An exception occurred. Exception description : [{0:S}].", ex.Message)); return false; } }
4.2 This method will perform the creation of a COM type library from the target assembly. After the type library has been created, it will be registered.
4.3 The TypeLibConverter class is used to perform the type library creation. Specifically the ConvertAssemblyToTypeLib() method.
4.4 An important part of calling ConvertAssemblyToTypeLib() has to do with the bit-ness of the type library to be created for the assembly :
4.5 A simple but effective way of determining whether RegisterAssembly is running as a 32-bit or 64-bit program is via testing the size of IntPtr as can be seen in the Is32Bits() and Is64Bits() methods.
4.6 Another point to note is the ITypeLibExporterNotifySink parameter for ConvertAssemblyToTypeLib().
4.7 This parameter must be supplied with an instance of a class that implements the ITypeLibExporterNotifySink interface. This object can be important in some circumstances. This interface contains 2 methods :
4.8 In a nutshell, the ResolveRef() method will be called when the assembly to be registered references a COM Type that is exported from another assembly instead of from an unmanaged type library. E.g. the COM type being defined as a managed type and is exposed to COM via COMVisibleAttribute. More will be explained in the next section (ConversionEventHandler).
4.9 When ResolveRef() is called, the referenced assembly is passed as parameter and ResolveRef() is expected to return an object which, at minimum, implements the ITypeLib interface. See ITypeLibExporterNotifySink.ResolveRef Method (Assembly).
4.10 I will be writing separate blogs in the future expounding ITypeLibExporterNotifySink implementations in greater detail. For the current blog and source codes, a default adequate one (ConversionEventHandler) is supplied and used (see next section).
4.11 The output of the ConvertAssemblyToTypeLib() method will be an object that implements the ICreateTypeLib and the ITypeLib interfaces :
5.1 The source codes for the ConversionEventHandler class is listed in ConversionEventHandler.cs :
// ConversionEventHandler.cs // Source codes for handling assembly reference resolution. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Configuration; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using System.Collections; using System.Collections.Specialized; using System.Reflection; using System.IO; namespace RegisterAssembly { public class ConversionEventHandler : ITypeLibExporterNotifySink { public ConversionEventHandler(bool bVerbose) { m_bVerbose = bVerbose; } public void ReportEvent(ExporterEventKind eventKind, int eventCode, string eventMsg) { // Handle the warning event here. if (m_bVerbose) { Console.WriteLine("ConversionEventHandler.ReportEvent() [eventKind : {0:S}] [eventCode : {1:D}] [eventMsg : {2:S}]", eventKind.ToString(), eventCode, eventMsg); } } public Object ResolveRef(Assembly asm) { try { // Resolve the reference here and return a correct type library. if (m_bVerbose) { Console.WriteLine("ConversionEventHandler.ResolveRef() [assembly : {0:S}]", asm.FullName); } string strAssemblyDirectory = Path.GetDirectoryName(asm.Location); string strAssemblyFileNameWithoutExtension = Path.GetFileNameWithoutExtension(asm.Location); string strTypeLibFullPath = strAssemblyDirectory + "\\" + strAssemblyFileNameWithoutExtension + ".tlb"; TypeLibConverter converter = new TypeLibConverter(); ConversionEventHandler eventHandler = new ConversionEventHandler(m_bVerbose); TypeLibExporterFlags flags; if (Program.Is32Bits()) { flags = TypeLibExporterFlags.ExportAs32Bit; } else if (Program.Is64Bits()) { flags = TypeLibExporterFlags.ExportAs64Bit; } else { Console.WriteLine(string.Format("Unknown bit-ness.")); return null; } ICreateTypeLib create_typeLib = null; try { create_typeLib = (ICreateTypeLib)(converter.ConvertAssemblyToTypeLib (asm, strTypeLibFullPath, flags, eventHandler)); } catch(Exception ex) { Console.WriteLine(string.Format("Unable to convert assembly [{0:S}] into a Type Lib. Exception description : [{1:S}]", strAssemblyFileNameWithoutExtension, ex.Message)); return null; } try { // SaveAllChanges() will create the TypeLib physical file // based on strTargetTypeLibFullPath. create_typeLib.SaveAllChanges(); } catch (Exception ex) { Console.WriteLine(string.Format("Unable to save TypeLib File [{0:S}] registered. Exception description : [{1:S}]", strTypeLibFullPath, ex.Message)); return null; } ITypeLib typelib = (ITypeLib)create_typeLib; UInt32 uiRetTemp = Program.RegisterTypeLib(typelib, strTypeLibFullPath, null); if (uiRetTemp == 0) { Console.WriteLine(string.Format("TypeLib File [{0:S}] registered.", strTypeLibFullPath)); } else { Console.WriteLine(string.Format("Failed to register TypeLib File [{0:S}]. Error code : [{1:D}]", strTypeLibFullPath, uiRetTemp)); return null; } return typelib; } catch(Exception ex) { Console.WriteLine(string.Format("An exception occurred. Exception description : [{0:S}].", ex.Message)); return null; } } private bool m_bVerbose = false; } }
The following are the pertinent points concerning this class (some points have been mentioned previously and are repeated) :
5.3 The ConversionEventHandler.ResolveRef() method is very similar to the PerformTypeLibCreationAndRegistration() method in that the TypeLibConverter.ConvertAssemblyToTypeLib() method is used to process an assembly :
Other implementations of ITypeLibExporterNotifySink may opt to search for an existing type library file, dynamically load it into memory, extract an ITypeLib object and then return it.
5.4 Note well that referenced assemblies which contain COM-visible types should be pre-registered (either via RegisterAssembly.exe or RegAsm.exe). Likewise, their associated type libraries should have been generated and registered before hand. Generally speaking a call to ResolveRef() can and should be avoided if possible.
6.1 If the RegisterAssembly program is required to unregister an assembly, PerformAssemblyUnregistration() will be used. The listing for this method can be found in AssemblyUnregistration.cs :
static bool PerformAssemblyUnregistration(string strTargetAssemblyFilePath) { try { RegistrationServices registration_services = new RegistrationServices(); Assembly assembly = Assembly.LoadFrom(strTargetAssemblyFilePath); bool bRet = false; bRet = registration_services.UnregisterAssembly(assembly); if (bRet) { Console.WriteLine(string.Format("Successfully unregistered assembly [{0:S}].", strTargetAssemblyFilePath)); } else { Console.WriteLine(string.Format("Failed to unregister assembly [{0:S}].", strTargetAssemblyFilePath)); } return bRet; } catch (Exception ex) { Console.WriteLine(string.Format("An exception occurred. Exception description : [{0:S}].", ex.Message)); return false; } }
6.2 It is simple. Just a call to the RegistrationServices.UnregisterAssembly() will do.
7.1 If an associated type library for the assembly exist and was registered, PerformTypeLibUnRegistration() is used to perform unregistration of this type library.
7.2 The source codes for PerformTypeLibUnRegistration() can be found in AssemblyUnregistration.cs :
static bool PerformTypeLibUnRegistration(string strTargetAssemblyFilePath) { try { Assembly assembly = Assembly.LoadFrom(strTargetAssemblyFilePath); Version version = assembly.GetName().Version; GuidAttribute guid_attribute = (GuidAttribute)assembly.GetCustomAttributes(typeof(GuidAttribute), false)[0]; Guid guid = new Guid(guid_attribute.Value); System.Runtime.InteropServices.ComTypes.SYSKIND syskind; if (Is32Bits()) { syskind = System.Runtime.InteropServices.ComTypes.SYSKIND.SYS_WIN32; } else if (Is64Bits()) { syskind = System.Runtime.InteropServices.ComTypes.SYSKIND.SYS_WIN64; } else { Console.WriteLine(string.Format("Unknown bit-ness.")); return false; } UInt32 uiRetTemp = UnRegisterTypeLib(ref guid, (UInt16)(version.Major), (UInt16)(version.Minor), 0, syskind); if (uiRetTemp == 0) { Console.WriteLine(string.Format("TypeLib File for assembly [{0:S}] unregistered.", strTargetAssemblyFilePath)); } else { Console.WriteLine(string.Format("Failed to unregister TypeLib File for assembly [{0:S}]. Error code : [{1:D}]", strTargetAssemblyFilePath, uiRetTemp)); return false; } return true; } catch (Exception ex) { Console.WriteLine(string.Format("An exception occurred. Exception description : [{0:S}].", ex.Message)); return false; } }
Note the following :
8.1 RegisterAssembly has been developed to allow for custom ITypeLibExporterNotifySink implementation objects to be used in the call to TypeLibConverter.ConvertAssemblyToTypeLib().
8.2 This is controlled in the RegisterAssembly.exe.config file :
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /> </startup> <appSettings> <add key="TypeLibExporterNotifySink" value="RegisterAssembly.ConversionEventHandler, RegisterAssembly" /> </appSettings> </configuration>
8.3 The default implementation is the ConversionEventHandler class which is supplied in ConversionEventHandler.cs.
8.4 Readers can write custom ITypeLibExporterNotifySink implementations and specify its use in the config file.
8.5 In upcoming blogs, I will be expounding more on ITypeLibExporterNotifySink and will provide a sample custom implementation.
8.6 The code that reads the config file and controls the program’s ITypeLibExporterNotifySink implementation can be found in ConfigSettings.cs :
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Runtime.InteropServices; using System.Configuration; using System.Collections.Specialized; namespace RegisterAssembly { partial class Program { const string DEFAULT_TYPELIB_EXPORTER_NOTIFY_SINK = "RegisterAssembly.ConversionEventHandler, RegisterAssembly"; static bool ReadConfigSettings() { NameValueCollection appSettings = ConfigurationManager.AppSettings; string strTypeLibExporterNotifySink = appSettings["TypeLibExporterNotifySink"]; if (string.IsNullOrEmpty(strTypeLibExporterNotifySink) == true) { strTypeLibExporterNotifySink = DEFAULT_TYPELIB_EXPORTER_NOTIFY_SINK; } Type type = Type.GetType(strTypeLibExporterNotifySink); if (type == null) { return false; } m_pITypeLibExporterNotifySink = (ITypeLibExporterNotifySink)(Activator.CreateInstance(type, new object[] { m_bVerbose })); if (m_pITypeLibExporterNotifySink == null) { return false; } return true; } } }
9.1 Two sets of test programs are supplied in the companion CodePlex site.
9.2 One is a pair of 32-bit code and the other 64-bit.
9.3 Remember that in order to process 64-bit code, RegisterAssembly.exe must be compiled as a 64-bit program.
9.4 Set this in the project properties under the “Build” section :
9.5 The 32-bit set includes the following projects :
To use this demonstration set together with RegisterAssembly.exe perform the following :
RegisterAssembly.exe CODE_BASE TYPELIB "<path>\ManagedClassLib32.dll"
Then perform an unregistration of ManagedClassLib32.dll with the following command line :
RegisterAssembly.exe UNREGISTER TYPELIB "<path>\ManagedClassLib32.dll"
9.6 Analogous results will be observed when the 64-bit test set is used.
10.1 RegisterAssembly.exe is an on-going project, a work in progress.
10.2 I consider it a basic version and more features can certainly be added to it.
10.3 I hope that the reader will comment on how to improve it further.
[转]Programmatically Register Assemblies in C#.
标签:purpose variables resolve instr define external nan eva nes
原文地址:http://www.cnblogs.com/czytcn/p/7928114.html