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

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read