Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Regasm2.exe – The .Net/COM+ Installation Tool.

0.00/5 (No votes)
22 Oct 2001 1  
This article describes how to design, build and install .Net Application into the COM+ Catalog without using the ServicedComponent class in your application. The solution shows retrieving the assembly and class attributes (included custom) from the assembly file and their storing into the COM+ Catal

Regasm2.exe

Introduction

The .Net Services (classes derived from the ServicedComponent class) can be installed into the COM+ 1.0 Application using the Microsoft command line tool - regsvcs.exe. This tool does all necessary work to run .Net Service class in the COM+ Catalog as a configured component COM. Based on the assembly and class attributes, the regsvcs tool will configure the application and component properties in the COM+ Catalog. The following snippet code shows an example of the empty boilerplate of .Net Queueable Service:

  [assembly: AssemblyKeyFileAttribute(@"..\..\ComponentQC.snk")]
  [assembly: ApplicationName("QCLogFile")]
  [assembly: ApplicationActivation(ActivationOption.Server)]
  [assembly: ApplicationQueuing(Enabled=true, QueueListenerEnabled=true)]
  [assembly: ApplicationAccessControl(Value = false, Authentication  = AuthenticationOption.None)]

namespace ComponentQC
{
  [Guid("BD294B3E-E9AF-47f9-9E12-1983DD42E1BB")]
  [InterfaceQueuing]
  public interface IQLogFile
  {
    void Write(string str);  
  }
	
  [Guid("254E1CE3-B0EC-4aaa-A0D9-34733ECB68F3")]
  [Transaction]
  [ObjectPooling(Enabled=true, MinPoolSize=2, MaxPoolSize=5)]
  [EventTrackingEnabled]
  public class QLogFile : ServicedComponent,  IQLogFile
  {
    public QLogFile() {}
    public void Write(string str) {}
    public override bool CanBePooled() { return true; }
    public override void Activate() {}
    public override void Deactivate() {}
  }
}

Everything is going straightforward until the situation when your design pattern needs to use different class inheritance (for instances: SoapHttpClientProtocol class, third party, etc.) and using the interface contract cannot be properly solution. Note that .Net does not allow multiple inheritance of implementation. This article describes a solution how to use your .Net class without using the ServicedComponent class and install it as a configured COM component. The solution shows retrieving the assembly and class attributes (included custom) from the assembly file and their storing into the COM+ Catalog Objects using the C# language.

Concept and Design.

The concept of this solution is based on the following steps:

  • Using the Interface contract of the COM+ Services such as IObjectControl, IObjectConstruct, IObjectConstructString, etc. instead of the class ServicedComponent.
  • Using the Microsoft Assembly Registration tool regasm.exe to register your .Net classes as non-configured COM components. It will be allow calling your classes transparently using the .Net Interop design pattern (the bridge between managed and unmanaged code).
  • Using the regasm2.exe tool (see below) to install non-configured COM components (which have been created in the second step) into the COM+ Catalog and configure them.

The first step is a design issue related with your application. For instance, the previously example using the COM+ interface contracts might look like the following:


  [assembly: AssemblyKeyFileAttribute(@"..\..\ComponentQC.snk")]
  [assembly: ApplicationName("QCLogFile")]
  [assembly: ApplicationActivation(ActivationOption.Server)]
  [assembly: ApplicationQueuing(Enabled=true, QueueListenerEnabled=true)]
  [assembly: ApplicationAccessControl(Value = false, Authentication  = AuthenticationOption.None)]

  [InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
  Guid("51372aec-cae7-11cf-be81-00aa00a2fa25")]
  public interface IObjectControl
  {
    void Activate();
    void Deactivate();
    bool CanBePooled();
  }

namespace ComponentQC
{
  [Guid("BD294B3E-E9AF-47f9-9E12-1983DD42E1BB")]
  [InterfaceQueuing] 
  public interface IQLogFile
  {
    void Write(string str);  
  }

  [Guid("254E1CE3-B0EC-4aaa-A0D9-34733ECB68F3")]
  [Transaction]
  [ObjectPooling(Enabled=true, MinPoolSize=2, MaxPoolSize=5)]
  [EventTrackingEnabled]
  public class QLogFile : IQLogFile, IObjectControl
  {
    public QLogFile() {}
    public void Write(string str) {}
    public override bool CanBePooled() { return true; }
    public override void Activate() {}
    public override void Deactivate() {}
  }
}

The changes are minor. The class instead of deriving from the ServicedComponent class using the IObjectControl interface contract in the callback manner.  All the remaining steps are participated for the post-build process and they can be incorporated into your make file. The other choice is to create a special .bat file, which it might look like the following:

rem unregister 
gacutil -u AttributeTester
regasm /u AttributeTester.dll  /tlb:AttributeTester.tlb 

rem register/update 
regasm AttributeTester.dll  /tlb:AttributeTester.tlb 
regasm2 AttributeTester.dll
gacutil -i AttributeTester.dll

Note that is very important to unregister your assembly and typelib. The otherwise your system will accumulate all versions of those registry (based on the GUID). The regasm2 tool seems to be like the extension step to the regasm. It will take only one argument, which is a name of the assembly. For the simplicity, the regasm2.exe file should be located in the same path than regasm.exe file. 

Also, it's a good practice to incorporate your post-build sequences into the build script and automate all process, it's important especially when you are dealing with COM+ Services.

Regasm2 Internals.

The regasm2 is a command line program with responsibilities to install and configure your application and its components into the COM+ Catalog. The program requires in prior access to the assembly and typelib files. Note that both files have to be located on the same path. The main process of the regasm2 is shown in the following snippet code:

  static int Main(string[] args) 
  { int retval = 0;

    try 
    {
      // display Logo

      Console.WriteLine();
      Console.WriteLine("The .Net/COM+ Installation Tool, Version 1.0");
      Console.WriteLine("(C) Copyright 2001, Roman Kiss, rkiss@pathcom.com");
      // validate the entry argument

      if(args.Length == 0 || File.Exists(args[0]) == false) 
      {
        throw new Exception("Missing the Assembly file.");
      }

      // Load the assembly

      Assembly assembly = Assembly.LoadFrom(args[0]);
      Console.WriteLine("Assembly file: ' {0} '", assembly.FullName);

      // Typelib file

      string tlbName = Path.ChangeExtension(args[0], "tlb");
      if(File.Exists(tlbName) == false) 
      {
        throw new Exception("Missing the typelib file.");
      }
      Console.WriteLine("Typelib  file: ' {0} ' ", tlbName);
      Console.WriteLine();

      // Install to the COM+ Catalog

      Console.WriteLine("Scanning Assembly for attributes ...");
      Install(assembly, tlbName);

      // finish	

      Console.WriteLine("Done");
      retval = 1;
    }
    catch (Exception ex)
    {
      Console.WriteLine("ErrorMessage: {0}", ex.Message);
      Console.WriteLine("StackTrace: {0}", ex.StackTrace);
    }

    return retval;
  }

The key task of the main function is invoking the Install(assembly, tlbName) function, which is actually "horse" function to provide all functionality of the regasm2. Its implementation is based on the scanning attributes in the assembly. The assembly is a magic place where sited all information (metada). Just we need them pickup and use them for our purpose - setting the catalog object's properties. Of course, it needs to know their access (abstract definition). Handling properties in the catalog is using the same mechanism for any objects such as application, component, roles, interfaces, etc. I will describe in details only the application object.

static void Install(Assembly ass, string typelib) 
{	
  ...

  COMAdminCatalog cat = new COMAdminCatalog();
  ICatalogCollection colA = cat.GetCollection("Applications") as ICatalogCollection;
  colA.Populate();
  ICatalogObject appl = colA.Add() as ICatalogObject;
		
  //////////////////////////////////////////////////////////////////////////////

  // Application. This process step will either create or update an Application 

  // entry in the COM+ Catalog

  //////////////////////////////////////////////////////////////////////////////

  foreach(object attr in ass.GetCustomAttributes(true) )
  {
    if(attr is ApplicationAccessControlAttribute)
    {
      ApplicationAccessControlAttribute a = attr as ApplicationAccessControlAttribute;
      appl.set_Value("ApplicationAccessChecksEnabled", a.Value);
      appl.set_Value("AccessChecksLevel", a.AccessChecksLevel);
      if(Enum.IsDefined(typeof(AuthenticationOption), a.Authentication)) 
      {
        appl.set_Value("Authentication", a.Authentication);
      }
      if(Enum.IsDefined(typeof(ImpersonationLevelOption), a.ImpersonationLevel)) 
      {
        // It seems to me a MS bug, and this is a workaround

        if(a.ImpersonationLevel == ImpersonationLevelOption.Default)
        {
          appl.set_Value("ImpersonationLevel", ImpersonationLevelOption.Impersonate);
        }
        else 
        {
          appl.set_Value("ImpersonationLevel", a.ImpersonationLevel);
        }
      }
    }

    // ...


    else
    if(attr is ApplicationCrmEnabledAttribute)
    {
      ApplicationCrmEnabledAttribute a = attr as ApplicationCrmEnabledAttribute;
      appl.set_Value("CRMEnabled", a.Value);
    }
    else
      AssemblyAttrExtension(attr, appl);    // extension for a custom assembly:attributes

  }
  // run-time adjustment:

  if(bIsApplNameExist == false) 
  {
    // use the short name of the assembly for application name

    appl.set_Value("Name", ass.FullName.Split(new char[] {','})[0]);
  }

  // now we can store it into the COM+ catalog

  colA.SaveChanges();		// save Application changes in the COM+ catalog

  Console.WriteLine(string.Format(" Step {0}. The Application '{1}' has been installed.", 
                                  step++, appl.Name));

// ...

}

Before actually scanning process we need to ask the COM+ catalog for a particular object. In our case (see the about snippet code) we created new one:

 ICatalogObject appl = colA.Add() as ICatalogObject;
This application object will automatically initiate its properties by default values. Now, during the scanning process for each found attribute these properties are going to be overwritten by your configuration values, for instance:
 appl.set_Value("Authentication", a.Authentication);

The scanning of the assembly attributes is finishing saving all changes into the catalog:

 colA.SaveChanges();	

The scanner has implemented all assembly and class attributes which are part of the .Net/COM+ Services. What about custom attributes? The scanner loop ends up in calling the chain function,

 AssemblyAttrExtension(attr, appl);

which it will allow to handle your custom attributes in the loosely coupled design pattern (using the System.Reflection) without adding their references. This is your place to put a business logic based on your custom attributes. Two custom assembly attributes are shown in the following snippet code:

static  void AssemblyAttrExtension(object assemblyAttr, ICatalogObject appl)
{	
  Type t = assemblyAttr.GetType();
  if(t.Name.ToString() == "ApplicationAdvancedAttribute")
  { 
    MethodInfo mi = t.GetMethod("get_RunForever");
    appl.set_Value("RunForever", mi.Invoke(assemblyAttr, null));
    mi = t.GetMethod("get_CreatedBy");
    appl.set_Value("CreatedBy", mi.Invoke(assemblyAttr, null));
    mi = t.GetMethod("get_Deleteable");
    appl.set_Value("Deleteable", mi.Invoke(assemblyAttr, null));
    mi = t.GetMethod("get_ShutdownAfter");
    appl.set_Value("ShutdownAfter", mi.Invoke(assemblyAttr, null).ToString());
  }
  else
  if(t.Name.ToString() == "CustomDescriptionAttribute")
  { 
    MethodInfo mi = t.GetMethod("get_Description");
    appl.set_Value("Description", mi.Invoke(assemblyAttr, null));
  }
}

Note that I created my CustomDescriptionAttribute attribute as a work around to the DescriptionAttribute, where are not accessors such as getter and setter for private field _desc.

Custom Attributes.

Any class derived from the Attribute class can be used as a xxxxAttribute to setup the metada in the assembly image. As an example of the custom attribute, I created the following attributes to setup advanced properties of the application such as RunForever, change ShutdownAfter time and CreatedBy.

Application file:

[assembly: ApplicationAdvanced(RunForever = true, ShutdownAfter = 60, CreatedBy = "Roman Kiss")]	
[assembly: CustomDescription("COM+ and C# Application Test")]

Implementation file:

using System;
using System.Runtime.InteropServices;

namespace LibOfCustomAttributes
{
  [AttributeUsage(AttributeTargets.Assembly)]
  public class ApplicationAdvancedAttribute : Attribute
  {
    private string _CreatedBy = "";
    private bool _Deleteable = true; 
    private bool _RunForever = false;
    private long _ShutdownAfter = 3;	// 3 minutes

    //

    public string CreatedBy { get { return _CreatedBy; } set { _CreatedBy = value;} }
    public bool Deleteable { get { return _Deleteable; } set { _Deleteable = value;} }
    public bool RunForever { get { return _RunForever; } set { _RunForever = value;} }
    public long ShutdownAfter { get { return _ShutdownAfter; } set { _ShutdownAfter = value;} }
  }

  [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class)]
  public class CustomDescriptionAttribute : Attribute
  {
    private string _Description = "";
    //

    public CustomDescriptionAttribute(string desc) 
    {
      Description = desc;
    }
    public string Description { get { return _Description; } set { _Description = value;}}
  }
}

Tips.

Using the .Net Services in your design the following tips are useful:

  • Use an interface contracts with your .Net classes. It will allow to use a loosely coupled design pattern and access from any COM/COM+ component.
  • Use explicitly GUIDs for each abstract definition such as assembly, classes, interfaces, which your registry will be updated instead of creating new ones.
  • Use the ServicedComponent class where it is possible and regsvcs.exe installation tool.
  • Automate your post-build process to be sure that your components have been updated.

Test.

The Regasm2 program can be tested using an empty application boilerplate such as AttributeTester and LibOfCustomAttributes projects:

using System;
using System.Diagnostics;
using System.EnterpriseServices;
using System.EnterpriseServices.CompensatingResourceManager;
using System.Runtime.InteropServices;
//

using RKiss.COMPlusInterfaces ;
//

using LibOfCustomAttributes;

[assembly: ApplicationID("E970F84A-6B45-49c3-B3F7-3C210BFA6E9B")]
[assembly: ApplicationName("AttributeTester")]
[assembly: ApplicationActivation(ActivationOption.Server)]
[assembly: Description("COM+ and C# Application Test")]
[assembly: ApplicationAccessControl(Value = false,  Authentication  = AuthenticationOption.None)]
[assembly: ApplicationQueuing(Enabled=false, QueueListenerEnabled=false)]
[assembly: SecurityRole("Roman", true, Description = "Security role test")]
[assembly: SecurityRole("John", true, Description = "Security role test")]
[assembly: ApplicationCrmEnabled]
[assembly: ApplicationAdvanced(RunForever = true, ShutdownAfter = 60, CreatedBy = "Roman Kiss")]	// custom attribute

[assembly: CustomDescription("COM+ and C# Application Test")]

namespace RKiss.COMPlusInterfaces 
{
#region Interfaces: IObjectControl, IObjectConstruct, IObjectConstructString 

  [InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
  Guid("51372aec-cae7-11cf-be81-00aa00a2fa25")]
  public interface IObjectControl
  {
    void Activate();
    void Deactivate();
    bool CanBePooled();
  }

  [InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
  Guid("41C4F8B3-7439-11D2-98CB-00C04F8EE1C4")]
  public interface IObjectConstruct
  {
    void Construct([In, MarshalAs(UnmanagedType.IDispatch)] object pCtorObj);
  }

  [InterfaceType(ComInterfaceType.InterfaceIsIDispatch),
  Guid("41C4F8B2-7439-11D2-98CB-00C04F8EE1C4")]
  public interface IObjectConstructString
  {
    string ConstructString { get; }	
  }
#endregion
}  

namespace AttributeTesterAB
{
  [Guid("F8D303FC-A118-4925-A6E5-39D5BB4C7774")]
  [Description("Test Component A")]
  [ObjectPooling(Enabled=true, MinPoolSize=2,MaxPoolSize=10)]
  [Transaction(TransactionOption.Disabled)]
  [ConstructionEnabled(Default="server=ATZ-ROMAN;uid=sa;pwd=;database=Logger")]
  [EventTrackingEnabled]
  [LoadBalancingSupported]
  [Synchronization(SynchronizationOption.RequiresNew)]
  [ExceptionClass("MyQCExceptionClass")]
  [CustomDescription("Test Component A")]
  public class TesterA : IObjectControl, IObjectConstruct
  {
    public TesterA() 
    { Trace.WriteLine(string.Format("[{0}] TesterA.TesterA", this.GetHashCode())); }
    public string Echo(string msg)
    { return string.Format("TesterA.Echo([{0}]", msg); }
    //IObjectConstruct

    public void Construct(object pCtorObj) 
    { 
      string constr = (pCtorObj as IObjectConstructString).ConstructString;
      Trace.WriteLine(string.Format("[{0}] TesterA.Construct = {1}", this.GetHashCode(), constr));
    }
    //IObjectControl

    public void Activate() 
    { Trace.WriteLine(string.Format("[{0}] TesterA.Activate", this.GetHashCode())); }
    public void Deactivate()
    { Trace.WriteLine(string.Format("[{0}] TesterA.Deactive", this.GetHashCode())); }
    public bool CanBePooled()
    {  
      Trace.WriteLine(string.Format("[{0}] TesterA.CanBePooled", this.GetHashCode()));
      return true;
    }
  }

  [Guid("FF1DBFA8-F1A6-408c-97A1-7E3AA1EFEBA1")]
  [Description("Test Component B")]
  [ObjectPooling(Enabled=false, MinPoolSize=5,MaxPoolSize=10)]
  [Transaction(TransactionOption.Supported)]
  [ConstructionEnabled(true, Default="server=ATZ-ROMAN;uid=sa;pwd=;database=Logger")]
  [EventTrackingEnabled]
  [Synchronization]
  [CustomDescription("Test Component A")]
  public class TesterB : IObjectControl, IObjectConstruct
  {
    public TesterB()
    { Trace.WriteLine(string.Format("[{0}] TesterB.TesterB", this.GetHashCode())); }
    public string Echo(string msg)
    { return string.Format("TesterB.Echo([{0}]", msg); }
    //IObjectConstruct

    public void Construct(object pCtorObj) 
    { 
      string constr = (pCtorObj as IObjectConstructString).ConstructString;
      Trace.WriteLine(string.Format("[{0}] TesterB.Construct = {1}", this.GetHashCode(), constr))
    }
    //IObjectControl

    public void Activate() 
    { Trace.WriteLine(string.Format("[{0}] TesterB.Activate", this.GetHashCode())); }
    public void Deactivate()
    { Trace.WriteLine(string.Format("[{0}] TesterB.Deactive", this.GetHashCode())); }
    public bool CanBePooled()
    { 
      Trace.WriteLine(string.Format("[{0}] TesterB.CanBePooled", this.GetHashCode()));
      return true;
    }
  }
}

Instructions:

  1. Create the assembly of the AttributeTester project and then start the InstallComponent.bat file located in this project. On the console you will see all installation progress (see console screen snap on the top of this article).
  2. Open the COM+ Explorer and check all expected configurations for the AttributeTester Application (roles, components and interfaces) in the catalog.
  3. Open the DebugView for Windows utility (http://www.sysinternals.com).
  4. Start Application AttributeTester.
The result might look like the following screen snaps:

COM+ Explorer

DebugView

Conclusion.

The assembly image is a very powerful source of the information related with all application abstract definitions, configuration, etc. In this article, we've seen how easy can be retrieved and used during the deploying time. Beside that you have a small tool, which can be useful as a part of your development utility library for Applications driven by .Net Services.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here