Tuesday, August 23, 2011

C++/CLI ActiveX automatic registration

I've been working on an ActiveX control written in C++/CLI recently and I thought I'd share the COM registration function ([ComRegisterFunction()] attribute) I've written (which is called by regasm.exe when you're registering the assembly for COM Interop).

It does the following:

Creates the necessary keys to register as an ActiveX control.
Adds in the codebase keys so there is no need to pass the /codebase flag to regasm.exe.

Please note, if you're not including oleidl.h as part of your include hierarchy (which I am as I'm using the Windows SDK), then you'll need to define the following constants:
// Ref: http://msdn.microsoft.com/en-us/library/ms678497.aspx

const int OLEMISC_RECOMPOSEONRESIZE = 1;
const int OLEMISC_CANTLINKINSIDE = 16;
const int OLEMISC_INSIDEOUT = 128;
const int OLEMISC_ACTIVATEWHENVISIBLE = 256;
const int OLEMISC_SETCLIENTSITEFIRST = 131072;
Credit is given to the authors of the CSActiveX control upon which this code was originally based.
//
// we need to add some extra registry keys on top of the regasm.exe generated
// keys. this is because we are an ActiveX control (not just a plain COM control)
// and as such require certain keys and values to be present.
//
// we also add in the codebase entries (instead of passing /codebase to regasm.exe)
// this is so we avoid the overkill warnings that regasm.exe generates.
//
// as such to register this .NET assembly as a fully fledged ActiveX control
// we need to run:
//
// regasm.exe /tlb
//
void FTEngine::Register( Type^ t )
{
  try
  {
    // Open the CLSID key of the control
    RegistryKey^ keyCLSID = Registry::ClassesRoot->OpenSubKey( "CLSID\\" +
                             t->GUID.ToString("B"), true );

    //////////////////////////////////////////////////////////////////////////
    // Set "InprocServer32" to register a 32-bit in-process server.
    // InprocServer32 =
    // Ref: http://msdn.microsoft.com/en-us/library/ms683844.aspx
    //
    RegistryKey^ subkey = keyCLSID->OpenSubKey( "InprocServer32", true );
    if( subkey )
    {
      // .NET runtime engine (mscoree.dll) for .NET assemblies
      subkey->SetValue( nullptr, Environment::SystemDirectory + "\\mscoree.dll" );

      // setup codebase flag as we're not in the GAC
      subkey->SetValue( "CodeBase", Assembly::GetExecutingAssembly()->CodeBase );

      // setup the version specific codebase flag
      Version^ ver = Assembly::GetExecutingAssembly()->GetName()->Version;
      subkey = subkey->OpenSubKey( String::Format( "{0}.{1}.{2}.{3}",
                                   ver->Major, ver->Minor, ver->Build,
                                   ver->Revision ), true );

      if( subkey )
      {
        // setup codebase flag as we're not in the GAC
        subkey->SetValue( "CodeBase",
                          Assembly::GetExecutingAssembly()->CodeBase );
      }
   }

   //////////////////////////////////////////////////////////////////////////
   // Create "Control" to identify it as an ActiveX Control.
   // Ref: http://msdn.microsoft.com/en-us/library/ms680056.aspx
   //
   keyCLSID->CreateSubKey( "Control" );

   //////////////////////////////////////////////////////////////////////////
   // Create "MiscStatus" to specify how to create/display an object.
   // MiscStatus =
   // Ref: http://msdn.microsoft.com/en-us/library/ms683733.aspx
   //
    subkey = keyCLSID->CreateSubKey( "MiscStatus" );

    int nMiscStatus = OLEMISC_RECOMPOSEONRESIZE +
                        OLEMISC_CANTLINKINSIDE + OLEMISC_INSIDEOUT +
                        OLEMISC_ACTIVATEWHENVISIBLE + OLEMISC_SETCLIENTSITEFIRST;

   subkey->SetValue( "", nMiscStatus.ToString(), RegistryValueKind::String );

   //////////////////////////////////////////////////////////////////////////
   // Create "ToolBoxBitmap32" to identify the module name and the resource
   // ID for a 16 x 16 bitmap as the toolbar button face.
   // ToolBoxBitmap32 = .,
   // Ref: http://msdn.microsoft.com/en-us/library/ms687316.aspx
   //
   subkey = keyCLSID->CreateSubKey( "ToolBoxBitmap32" );

   // If you want different icons for each control in the assembly you
   // can modify this section to specify a different icon each time.
   // Each specified icon must be embedded as a win32 resource in the
   // assembly; the default one is at the index 101, but you can use
   // additional ones.
   subkey->SetValue( "",
                     Assembly::GetExecutingAssembly()->Location + ", 101",
                     RegistryValueKind::String );

   //////////////////////////////////////////////////////////////////////////
   // Create "TypeLib" to specify the typelib GUID associated with the class.
   //
   subkey = keyCLSID->CreateSubKey("TypeLib");

   Guid libId = Marshal::GetTypeLibGuidForAssembly( t->Assembly );
   subkey->SetValue( "",
                     libId.ToString("B"),
                     RegistryValueKind::String );  


   //////////////////////////////////////////////////////////////////////////
   // Create "Version" to specify the version of the control.
   // Ref: http://msdn.microsoft.com/en-us/library/ms686568.aspx
   //
   subkey = keyCLSID->CreateSubKey( "Version" );

   int nMajor, nMinor;
   Marshal::GetTypeLibVersionForAssembly( t->Assembly, nMajor, nMinor);
   subkey->SetValue( "",
                     String::Format( "{0}.{1}", nMajor, nMinor ) );
   }
 catch( Exception^ ex )
 {
   LogException( ex );
   throw;
 }
}

No comments: