Showing posts with label regasm. Show all posts
Showing posts with label regasm. Show all posts

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;
 }
}

Thursday, July 28, 2011

Visual Studio (all editions including Express) on 64bit Windows may cause headaches when using "Register for COM Interop"

Just experienced a wasted 60 minutes trying to figure out why a C# project I've created (which is registered for COM Interop / CCW) couldn't be created from my test harness .vbs file (via CreateObject).

Background: I installed Visual C# 2010 Express on a 64bit Windows 7 platform, created my demo C# 'COM project', told VS to 'Register for COM Interop' - it all built without fail. However when I try and call into the COM object via a test.vbs file COM was complaining!

After some investigation (the /regfile switch on regasm.exe helped me out) I noticed that VS was using the 32bit version of regasm.exe which was subsequently writing the registry entries under the 'virtualised' WOW64 hierarchy (i.e. HKEY_LOCAL_MACHINE\Software\Wow6432Node) . Thus when I execute my .vbs (which internally uses the 64bit cscript.exe) then it couldn't find the COM object as registered (as it was only registered for the 32bit world).

Solution is to manually run the 64bit regasm.exe so the registry entries are put into the normal (and correct) place. Then your COM object will be visible to the 64bit world!

See here:-
http://support.microsoft.com/kb/956933