Replaceable Parameters for ATL Registrar



Click here for larger image

The arrow points to IE Extension button. Button icons were registered using replaceable ATL Registrar parameter

Environment: VC6, IE5.0 and above

Source code for this article contains a skeleton of Internet Explorer COM Extension created with ATL. To invoke Extension you can add a button to IE toolbar. Clicking it will invoke your code. Location for button's regular and hot icons must be registered along with other items that declare your COM object as IE Extension.

ATL Registrar comes with %MODULE% replaceable parameter. During registration it will be replaced with the full path of your component. However, I wanted to keep button icons in images subfolder, relative to the installation folder of my component. In other words, I wanted a new replaceable parameter, call it %MODULEPATH%, that I can use as in the following excerpt from .rgs script file:

HKLM
{
 SOFTWARE
 {
  Microsoft
  {
   'Internet Explorer'
   {
     NoRemove Extensions
     {
      ForceRemove {B7FE5D70-9AA2-40F1-9C6B-12A255F085E1}
      {
       val 'Default Visible' = s 'Yes'
       val 'ButtonText' = s 'My button'
       val 'MenuText' = s 'My menu'
       val 'MenuStatusBar' = s 'This will call my COM object'
       val 'HotIcon' =  s '%MODULEPATH%\images\buttonhot.ico'
       val 'Icon' =  s '%MODULEPATH%\images\button.ico'
       val 'CLSID' = s '{1FBA04EE-3024-11d2-8F1F-0000F87ABD16}'
       val 'ClsidExtension' = s '{B7FE5D70-9AA2-40F1-9C6B-12A255F085E1}'
      }
    }
   }
  }
 }
}

To enable component registration ATL Wizard automatically adds a macro like:

DECLARE_REGISTRY_RESOURCEID(IDR_IECMDEXECUTE)

to your COM object class declaration. This macro expands into:

static HRESULT WINAPI UpdateRegistry(BOOL bRegister)
{
   return _Module.UpdateRegistryFromResource(IDR_IECMDEXECUTE,
                                             bRegister);
}

ATL DllRegisterServer implementation walks the list of COM objects declared in object map and invokes UpdateRegistry for each. UpdateRegistryFromResource is a symbol defined as either UpdateRegistryFromResourceD or UpdateRegistryFromResourceS method of CComModule. D and S suffixes stand for dynamic or static linking with Registrar component. For our purposes it is important that both of these methods have a third parameter, default value of which is NULL:

HRESULT WINAPI UpdateRegistryFromResourceD(LPCTSTR lpszRes,
                                           BOOL bRegister,
               struct _ATL_REGMAP_ENTRY* pMapEntries = NULL)

MSDN Library documentation points to pMapEntries paramater as the way that ATL designers provided to customize Registrar with additional replaceable parameters in rgs script. _ATL_REGMAP_ENTRY is defined like this:

struct _ATL_REGMAP_ENTRY
{
  LPCOLESTR     szKey;
  LPCOLESTR     szData;
};

Now we know everything we need to know to start with the implementation. Because DLLRegisterServer can be called as soon as DLL is loaded, DLLMain is the best place to prepare all the data required to set up _ATL_REGMAP_ENTRY. In this sample we need only module path so we will store it in global variable szModulePath as shown in the code excerpt below:

OLECHAR szModulePath[ MAX_PATH]; // module path without terminating

CComModule _Module;

BEGIN_OBJECT_MAP(ObjectMap)
OBJECT_ENTRY(CLSID_IECmdExecute, CIECmdExecute)
END_OBJECT_MAP()

///////////////////////////////////////////////////////////////////
// DLL Entry Point

extern "C"
BOOL WINAPI DllMain( HINSTANCE hInstance,
                     DWORD dwReason,
                     LPVOID /*lpReserved*/)
{
  if (dwReason == DLL_PROCESS_ATTACH)
  {
    TCHAR ModulePath[MAX_PATH];

    // Find and store this DLL module path as OLECHAR array

    ::GetModuleFileName( hInstance, ModulePath, MAX_PATH);
    int len = _tcslen( ModulePath);
    for ( int i = len; i > 0; i--)
      if ( ModulePath[i] == _T('\\'))
      {
        USES_CONVERSION;
        ModulePath[i] = _T('\0');

        OLECHAR* pszModulePath = T2OLE(ModulePath);
        wcscpy( szModulePath, pszModulePath);
        break;
      }

      _Module.Init(ObjectMap, hInstance, &LIBID_IEEXTENSIONLib);
      DisableThreadLibraryCalls(hInstance);
  }
  else if (dwReason == DLL_PROCESS_DETACH)
      _Module.Term();
  return TRUE;    // ok
}

We can not use DECLARE_REGISTRY_RESOURCEID macro anymore because we want to pass in the array of _ATL_REGMAP_ENTRY structures, here declared as the static member of the class:

static _ATL_REGMAP_ENTRY RegEntries[];
// Default implementation does not pass _ATL_REGMAP_ENTRY 
//    array but uses default value NULL
// DECLARE_REGISTRY_RESOURCEID(IDR_IECMDEXECUTE)
static HRESULT WINAPI UpdateRegistry(BOOL bRegister)
{
  return _Module.UpdateRegistryFromResource(IDR_IECMDEXECUTE,
                                            bRegister,
                                            RegEntries);
}

Finally, we need to initialize the array:

extern OLECHAR szModulePath[];

_ATL_REGMAP_ENTRY CIECmdExecute::RegEntries[] = 
     { { OLESTR("ModulePath"), szModulePath}, { NULL, NULL} };

and we are done. UpdateRegistryFromResource will take care of substituting each occurence of %MODULEPATH% in rgs script with the run-time path of our COM module. Note that the case of strings is not important. You can now keep adding new array elements and new replaceable paramaters according to your specific registration requirements.

Once you register IE Extension component you will be able to invoke your code by clicking the Extension toolbar button and/or menu item. Check rgs declarations at the beginning of this article to see Registry entries required to get IE to display Extension menu item along with the toolbar button. Menu Item will be added to Tools menu. To enable call into your code Extension COM component must implement IOleCommandTarget interface. IE will call Exec method on that interface. MSDN Library contains contradictory information about the ID of the command that will be invoked and does not give you clues about required implementation of the second IOleCommandTarget method - QueryStatus. Download demo project source code to see the implementation that worked for me. Note also that I used macro:

DECLARE_CLASSFACTORY_SINGLETON(CIECmdExecute)

to make sure IE would not create a new Extension object for each Exec method call. Of course, this is not required and depends on what exactly do you intend to do in your Extension component when Execmethod is called and whether you need to maintain object state dependent on the number of calls. The demo project for this article was generated with ATL Wizard so it will register your component after each build. If you want to see your Extension's button while developing in VS, change button icons rgs entries to:

  val 'HotIcon' =  s '%MODULEPATH%\..\buttonhot.ico'
  val 'Icon' =  s '%MODULEPATH%\..\button.ico'

This article came about as I was working on larger project which incorporates both Internet Explorer Extension and Helper Object. I may update it later on.

Downloads

Download demo project source - 11 Kb


Comments

  • Useful tip

    Posted by kirants on 12/19/2005 08:50pm

    Found this tip useful. You have presented the problem and the solution in a very easy to understand way. Kudos !!

    Reply
  • It isn't executed why from win98?

    Posted by Legacy on 10/29/2003 12:00am

    Originally posted by: lee ken jun

    i tested 2000,xp
    but, It is not executed from win98
    It means that I can a gain and loss?

    Reply
  • What's wrong with icons in same dll module?

    Posted by Legacy on 11/28/2001 12:00am

    Originally posted by: Alexander Fedorov

    For IE Extensions you can use icons that resides in resources of your extention module...
    something like this:
    HotIcon="C:\PROGRA~1\YAHOO!\MESSEN~1\YPAGER.EXE,105"
    or just
    HotIcon=",4"
    What is the possible reason to keep this icons in separate files?

    Reply
  • Great Article, Great Code!

    Posted by Legacy on 11/27/2001 12:00am

    Originally posted by: Bill SerGio

    I really like your code and sample and it is good to see someone FINALLY post something like this--BUT, I have a CHALLENGE for you!
    I have been placing buttons inside of I.E. for awhile now but I still have NOT figured out how to get the button to APPEAR automatically in the toolbar?
    I have forced the button to appear without setting it through options by giving it the identity of one of the standard I.E. buttons and tricking I.E. but this is not very elegant a solution!
    Do you have any tricks for getting the button to appear automatically?

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Today's agile organizations pose operations teams with a tremendous challenge: to deploy new releases to production immediately after development and testing is completed. To ensure that applications are deployed successfully, an automatic and transparent process is required. We refer to this process as Zero Touch Deployment™. This white paper reviews two approaches to Zero Touch Deployment--a script-based solution and a release automation platform. The article discusses how each can solve the key …

  • On-demand Event Event Date: December 18, 2014 The Internet of Things (IoT) incorporates physical devices into business processes using predictive analytics. While it relies heavily on existing Internet technologies, it differs by including physical devices, specialized protocols, physical analytics, and a unique partner network. To capture the real business value of IoT, the industry must move beyond customized projects to general patterns and platforms. Check out this webcast and join industry experts as …

Most Popular Programming Stories

More for Developers

RSS Feeds