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

  • Live Event Date: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

  • On-demand Event Event Date: October 29, 2014 It's well understood how critical version control is for code. However, its importance to DevOps isn't always recognized. The 2014 DevOps Survey of Practice shows that one of the key predictors of DevOps success is putting all production environment artifacts into version control. In this webcast, Gene Kim discusses these survey findings and shares woeful tales of artifact management gone wrong! Gene also shares examples of how high-performing DevOps …

Most Popular Programming Stories

More for Developers

RSS Feeds