Topic 4.2

COM Registration

Registering Components with RegDLL & RegSrv32.exe

Components

In short, COM components are binary files of compiled code that an executable program relies on in order to carry out it's functions. For instance, a dllAn acronym for Dynamic Link Library is a collection of small executable code, which can be called upon when needed by the executable program. The DLL lets the executable communicate with a specific device such as a printer or may contain code to do any number of particular functions. The main advantage that I'm aware of is when and/or if the DLL is called, then it is loaded which means less resource intensive load on memory—better known as ramAn acronym for Random Access Memory. These files can be in differing file extensions, i.e.  .ocx or even .cpl. Here's a bit more detail on the more common extensions:

  • ActiveX Control Files (*.ocx)–Can perform a variety of reusable functions and are comparable to Java applets in their functionality. First introduced by Microsoft in 1996 as part of their Component Object Model (COM) and Object Linking and Embedding (OLE) technologies.
  • Control Panel Files (*.cpl)–There is a control panel file for every item located or listed in the Windows Control Panel. Each is a specialized file that may reference zero or more additional DLLs.
  • Device Driver Files (*.drv)–There are one or many device driver files for each hardware component installed on a Windows PC. One of the more prevalent are those installed for printers or network adapters; i.e. when you plug in a flash drive in a PC for the first time, that system takes a moment to install the required drivers.

Other files which are less common that can be registered with COM include Java class files (*.class) and ASP or WSH scriptlets for use on IIS web servers (*.wsc).

If you're using my portable compiler, Pac-Man, I have eliminated the need to use the custom.nsh file to register your components. You can visit the GitHub project page here which will take you to a brief summary for more information.
Registering Files

In order for the host operating system to find a component when a program needs it, it must be registered with the system allowing it to be recorded within the registry.

A component is usually registered by using the Regsvr32 (REGSVR32.EXE) command line tool. This tool assumes that the component being registered has been properly coded to support the DLLRegisterServer() public method. When executed it should respond with a dialog box indicating success or failure of the registration.

CLI Example
REM Using the command prompt enter the following:
regsvr32 C:\Windows\system32\wshcon.dll
REM The system should respond with a dialog box saying:
REM DllRegisterServer in C:\Windows\system32\wshcon.dll succeeded

REM Running the following command in command prompt will output:
REM aclui.dll was loaded, but the DllRegisterServer entry point was not found.
REM This file can not be registered
regsvr32 C:\Windows\system32\aclui.dll
REM This is because this DLL is not set up to self-register.

Like REGSVR32.EXE, MSIEXEC.EXE can self-register COM objects as well but this is usually carried out automatically by msiAn installer package file format used by Windows. packages.

Your portable launcher should be able to recognize the component's self-registration entries and not just read in the values as registry keys which must be set during the importing of the portable application's settings, even if it has set the component to self-register as well.

Take note of the following as they are common mistakes when creating Registry entries for COM registrations:

  • Registry entries under the HKEY_CLASSES_ROOT hive.
  • The program returns errors similar to: Reg key 'primarykey1' is used in an unsupported way. ProgId should be registered via the ProgId table. (Or you'll get CLSIDs, CLSID associations, file associations, Version Independent ProgID, Shell extension verbs, or TypeLibs).
  • Conflicting Registry entries because portable launchers are directly writing the COM registrations into the Registry for the same or similar DLLs, even though they are in different paths.
Registry Keys

Before we jump into registering files, we should better understand how the HKEY_CLASSES_ROOT (HKCR) Registry tree works. Bare with me but the HKCR root doesn't technically exist; it is a shortcut to other sections of the Registry filesystem. On Windows NT 4.0 or earlier, HKCR only points to HKEY_LOCAL_MACHINE (HKLM) \SOFTWARE\Classes. On Windows 2000 and later, there may also be HKEY_CURRENT_USER (HKCU) \SOFTWARE\Classes entries which override the HKLM ones; these are created when the user installs (or is "assigned") an application but does not have the rights to install it for all users (in HKLM\SOFTWARE\Classes). In the following notes I will use HKCR as the base of the Registry keys, but please understand that this most often (and best) refers to HKLM\SOFTARE\Classes\

GUIDs/CLSIDs

A Class ID (CLSID), or class identifier, is a 128-bit (large) number expressed in hexidecimal surrounded by curly-brackets. They represent a unique ID for a program or application component. Think of a CLSID as a social security number but for an application or its component. CLSIDs are used by Windows to identify software components without having to know their name. They can also be used by applications to identify a computer, file or other similar information. These IDs are generated by using the current time, network adapter address (if present), and other items on a computer so that no two numbers will ever be the same.

The registry entries for any registered component are usually located in the HKEY_CLASSES_ROOT\CLSID section of the registry. Each entry under CLSID is a subtree whose name is the curly-bracketed GUID?GUIDs are used to identify components and interfaces in the form of class IDs (CLSID) and interface IDs (IID). You can find out more by visiting this article. (i.e. HKCR\CLSID\{00000000-0000-0000-0000-000000000000}). If a component has been properly registered, it will have a subtree underneath this called InProcServer32. The default value of the InProcServer32 subtree is the full path to the component which corresponds to that GUID. This value may be one of two types; REG_SZ or REG_EXPAND_SZ. If its REG_EXPAND_SZ, the path will typically contain an environment variable such as %SystemRoot% in the path name.

A developer has full control over what happens when you call RegSvr32. Usually they will register COM controls in HKCR, but they don't have to. They can do whatever they feel like doing and there's no absolute way for you to tell whether they've done it or not.

One of the following values must exist to instruct the system where to find the component:

  • HKCR\CLSID\{CLSID}\LocalServer = full path to 16-bit file
  • HKCR\CLSID\{CLSID}\LocalServer32 = full path to 32-bit file
  • HKCR\CLSID\{CLSID}\InprocServer = full path to 16-bit file
  • HKCR\CLSID\{CLSID}\InprocServer32 = full path to 32-bit file

Other optional values include:

  • HKCR\CLSID\{CLSID}\(default) = human-readable class name
  • HKCR\CLSID\{CLSID}\DefaultIcon = path to file/icon index
  • HKCR\CLSID\{CLSID}\Insertable
  • HKCR\CLSID\{CLSID}\Interface
  • HKCR\CLSID\{CLSID}\ProgID = ProgID
ProgID

The ProgID, or program identifier, is a Registry entry that can be associated with a CLSID. Its format is as follows: Program.Component.Version, such as Word.Document.8 which refers to an MS Word XP/2002 document. Here's the format:

  • HKCR\ProgID\
  • HKCR\ProgID\(default) = human readable component name
  • HKCR\ProgID\CLSID\(default) = CLSID
  • HKCR\ProgID\...

There are also version-independent program ID's with the format Program.Component, as follows:

  • HKCR\Versionless-ProgID\
  • HKCR\Versionless-ProgID\CLSID\ = CLSID
  • HKCR\Versionless-ProgID\CurVer = ProgID
File Assoc.

The File Extension assists the OS in knowing which program to run when you open a document with that extension by referring documents of a certain file extension to the appropriate ProgID. For example, when you double-click a Word *.doc file, the system looks up the extension *.doc and finds the reference to the ProgID Word.Document.8 where it can find the full path to the executable program msword.exe.

  • HKCR\CLSID\.ext\(default) = ProgID
Interface

If the component needs to register an interface, it requires the following syntax:

  • HKCR\Interface\IID\
  • HKCR\Interface\IID\BaseInterface
  • HKCR\Interface\IID\NumMethods
  • HKCR\Interface\IID\ProxyStubCLSID or ProxyStubCLSID32 = CLSID
AppID

Some EXE components group their registration information together with other related components, under a single AppID, as follows. The AppID is a string which looks like a filename, e.g. “MYAPP.EXE”.

  • HKCR\AppID\AppID\
  • HKCR\AppID\AppID\AppID = AppID
  • HKCR\AppID\AppID\DllSurrogate or DllSurrogateExecutable = path to file
  • HKCR\AppID\AppID\LocalService or ServicePartameters
  • HKCR\AppID\AppID\...
TypeLib

Type Libraries are required by some COM objects and are included in a separate file with an extension of *.tlb. The following Registry keys are also set in this case:

  • HKCR\Interface\IID\TypeLib\(default) = TypeLib
  • HKCR\TypeLib\TypeLib\#.#\#\win32\(default) = path to .tlb file
  • HKCR\TypeLib\TypeLib\...
Implementation

Now that all that is out of the way we can focus on the fun stuff. If you need to register/unregister any COM component files you can do so a couple of ways. The first of which we'll be discussing is how to do so using the Regsvr32 command line. Windows PCs with Internet Explorer 3.0 or later have Regsvr32.exe. So trust me when I say there's a good chance your PC came stock with this. If you are running on a x64 computer, there are two variants you can consider. They can be found in either $WINDIR\system32 or in $WINDIR\SysWow32.

The parameters you can use with RegSrv32 are /u /s /i /n. The /u command switch will unregister the file. The /i switch can be used with /u to call for DLL uninstallation. The /n parameter will not call DllRegisterServer; it's used with /i which is the install switch. If you use /s, which means silent, no message boxes will be displayed on Windows XP or later.

When using Regsvr32.exe from the command line you'll get a dialog box after calling it. The DllSelfRegister function will be invoked unless using the aforementioned switch of course; if successful an alert box will be shown denoting it's success—as the same for failure which throws an error message.

It's been my experience that the x64 RegSvr32.exe registers x86 DLL's properly on Windows Vista?Won't work on Windows XP. You can find out more by visiting this article. and above so I use it when installing on x64 systems even when registering a x86 file for PAFs that can't run on WinXP and older anyway.

That's just my opinion but you may use what works for you.

NSIS Example
;= Regardless of architecture we're using just the following
!define REGSVR `$SYSDIR\regsvr32.exe` ;= define where RegSrv32 is
!define DLL `$AppDirectory\App\MyLegalProgram\myLegit.dll` ;= define the file to register
;=#
;= Command line usage is the same for both variants of RegSrv32 as follows
;= regsvr32 [/u] [/s] [/n] [/i[:cmdline]] DLL
;=#
;= So in our custom.nsh file it would be similar too the following
Exec `"${REGSVR}" /s "${DLL}"`	;= notice the lack of the /i switch
;= That's because we aren't actually installing it
;= Additionally you may also use the following
ExecWait `"${REGSVR}" /s "${DLL}"` $0 ;= The $0 will contain the error code if any
;= The above will wait for exe to quit it's process before continuing
RegDLLView
To find out all the information that you will need from a DLL file for registering, I use a little program called RegDLLView by NirSoft. This makes finding the GUID, ProgID, and CLSID ridiculously easy.

If you're PAFing with my build of PAL then all you will need is the ProgID and the file path of the DLL(s) that need registering. If you haven't already done so, grab both variants of RegDLLView (x32 & x64—if you do not have a 64-bit machine do not worry about it but you're obviously limited to only building 32-bit compliant PAFs). Once you've got RegDLLView, go on and launch the 64-bit version (don't worry about trash or clean up.. RegDLLView is completely portable by itself) and wait for it to finish loading (refer to the bottom, left corner of the window; you should see something like, "Loading..."). Once it's all loaded, look for the column Last Registered On in the top window pane and click on it until the list is sorted to the most recent date.

Now you can look for your applications DLL files. Scan for your application's company name. If there isn't anything for your application then close out RegDLLView and open the 32-bit version and repeat the previous steps. If you do find something (there can be more than one) that relates to your application, refer to the file path column and locate that DLL. If the DLL's location is inside the application folder then no furthur action is required on your part because the DLL will be in the %PAL:AppDir%\AppName folder anyway. However, if the DLL is not located in the application folder and it is somewhere like $SYSDIR\DynamicLibrary.dll than you're going to need to copy the DLL into your PAF's folder (i.e. ..\App\DefaultData so you can access them from ..\%PAL:DataDir% as well as have a back up if needed later).

After you've copied the DLL you can now select the DLL reference in the top window pane of RegDLLView. Notice in the bottom window pane there is now one or more lines of data in regards to this DLL file. You'll only need the first line as this is where you can find the ProgID and that is what we want! Now in the Launcher.ini you just need the ProgID and the file path to the DLL(s) you want to register before launching the actual application. So you can now add the section [RegesterDLL1] and [RegesterDLL2] and so on depending on how many you might need. Your Launcher.ini file should have something similiar to this:

[RegisterDLL1]
ProgID=MyAppControlPanel
File=%PAL:AppDir%\controller.cpl

[RegisterDLL2]
ProgID=DynamicLibrary
File=%PAL:DataDir%\dynlib.dll
Macros

The following are the macros that I've written for use in the RegDLLs.nsh segment—in both the official builds of PAL and in my own variant of PAL.

DLL::GetGUID

Discription
The following macro is one I wrote which uses the System plugin. What this does is returns a DLLs GUID by means of its ProgID. Use RegDLLView to find a DLLs ProgID If it doesn't find a GUID an error flag is set to which you may check for.
;=# 
; ${DLL::GetGUID} $0 "PROGID"
;
;	$0     = Will return the GUID
;	PROGID = The DLLs ProgID
;
!define DLL::GetGUID "!insertmacro _DLL::GetGUID"
!macro _DLL::GetGUID _RETURN _ProgID
	System::Call `ole32::CLSIDFromProgID(w,&g16)i("${_ProgID}",.r0).r2`
	StrCmp $2 "-2147221005" 0 +4
	SetErrors
	StrCpy ${_RETURN} ""
	Goto +2
	StrCpy ${_RETURN} $0
!macroend

DLL::Backup

The following macro tries to use what we just discussed above. I do this because the GUID allows you to go straight to the registry branch (usually in HKCR\CLSID but can be found in HKLM\Software\Classes\CLSID as well) to find a DLLs location instead of having to search the registry recursively through routines that are time consuming and a waste of space.

Discription
This macro will locate the path to a locally registered DLL using the GUID of that DLL. If the location is found it will then write the value of its location to the PortableApps.comLauncherRuntimeData.ini file for later use and unregisters the DLL.
;=#
; ${DLL::Backup} "GUID" /DISABLEFSR "SECTION" "KEY" $0 $1
;
;    ::Backup     = Makes a backup of a local DLL file. 
;    GUID         = GUID | i.e. {00000000-0000-0000-0000-000000000000}
;    /DISABLEFSR  = Disables redirection if x64. Use "" to skip.
;    SECTION      = The section in the INI file.
;    KEY          = The key to write the value to in the INI file.
;    $0           = Return after call
;    $1           =   ''    ''    ''
;
!define DLL::Backup "!insertmacro _DLL::Backup"
!macro _DLL::Backup _GUID _FSR _SECTION _KEY _ERR1 _ERR2
	ClearErrors
	ReadRegStr $0 HKLM `SOFTWARE\Classes\CLSID\${_GUID}\InprocServer32` ""
	${IfNot} ${Errors}
		StrCmpS $Bit 64 0 +6
		StrCmp ${_FSR} /DISABLEFSR 0 +5
		${WriteRuntimeData} ${_SECTION} ${_KEY} `$0`
		ExecDos::Exec /TOSTACK /DISABLEFSR `"${REGSVR}" /s /u "$0"`
		Goto +2
		ExecDos::Exec /TOSTACK `"${REGSVR}" /s /u "$0"`
		Pop ${_ERR1}
		Pop ${_ERR2}
	${EndIf}
!macroend

DLL::Restore

Discription
This macro will read a value from PortableApps.comLauncherRuntimeData.ini to see if a locally installed DLL was found in ${SegmentPre} and if so will then proceed to register that DLL file again.
;=#
; ${DLL::Restore} /DISABLEFSR "SECTION" "KEY" $0 $1
;
;    ::Restore     = Restores a local DLL file from a backup. 
;    /DISABLEFSR   = Disables redirection if x64. Use "" to skip.
;    SECTION       = The section in the INI file.
;    KEY           = The key to find the value from in the INI file.
;    $0            = Return after call
;    $1            =   ''    ''    ''
;
!define DLL::Restore "!insertmacro _DLL::Restore"
!macro _DLL::Restore _FSR _SECTION _KEY _ERR1 _ERR2
	ClearErrors
	${ReadRuntimeData} $0 ${_SECTION} ${_KEY}
	${IfNot} ${Errors}
		StrCmpS $Bit 64 0 +4
		StrCmp ${_FSR} /DISABLEFSR 0 +3
		ExecDos::Exec /TOSTACK /DISABLEFSR `"${REGSVR}" /s "$0"`
		Goto +2
		ExecDos::Exec /TOSTACK `"${REGSVR}" /s "$0"`
		Pop ${_ERR1}
		Pop ${_ERR2}
	${EndIf}
!macroend

DLL::Register

Discription
This macro will check the archutechture of the host PC and registers a DLL file accordingly.
;=#
; ${DLL::Register} "DLL" /DISABLEFSR $0 $1
;
;    ::Register		= Registers a specified DLL file.
;    DLL			= Path to DLL | i.e. "$AppDirectory\Program\file.dll"
;    /DISABLEFSR	= Disables redirection if x64. Use "" to skip.
;    $0				= Return after call
;    $1				=   ''    ''    ''
;
!define DLL::Register `!insertmacro _DLL::Register`
!macro _DLL::Register _DLL _FSR _ERR1 _ERR2
	StrCmpS $Bit 64 0 +4
	StrCmp ${_FSR} /DISABLEFSR 0 +3
	ExecDos::Exec /TOSTACK /DISABLEFSR `"${REGSVR}" /s "${_DLL}"`
	Goto +2
	ExecDos::Exec /TOSTACK `"${REGSVR}" /s "${_DLL}"`
	Pop ${_ERR1}
	Pop ${_ERR2}
!macroend

DLL::UnRegister

Discription
This macro will check the archutechture of the host PC and unregisters a DLL file accordingly.
;=#
; ${DLL::UnRegister} "DLL" /DISABLEFSR $0 $1
;
;    ::UnRegister	= Unregisters a specified DLL file.
;    DLL			= Path to DLL | i.e. "$AppDirectory\Program\file.dll"
;    /DISABLEFSR	= Disables redirection if x64. Use "" to skip.
;    $0				= Return after call
;    $1				=   ''    ''    ''
;
!define DLL::UnRegister `!insertmacro _DLL::UnRegister`
!macro _DLL::UnRegister _DLL _FSR _ERR1 _ERR2
	StrCmpS $Bit 64 0 +4
	StrCmp ${_FSR} /DISABLEFSR 0 +3
	ExecDos::Exec /TOSTACK /DISABLEFSR `"${REGSVR}" /s /u "${_DLL}"`
	Goto +2
	ExecDos::Exec /TOSTACK `"${REGSVR}" /s /u "${_DLL}"`
	Pop ${_ERR1}
	Pop ${_ERR2}
!macroend

Example Usage

Discription
Below is an example custom.nsh file with the above four macros which will show how to use these in action.

;= DEFINES
;= ################
!define DLL32	`$AppDirectory\file32.dll`
!define DLL32ID	`{CDC95B92-E27C-4745-A8C5-64A52A78855D}`
!define DLL64	`$AppDirectory\file64.dll`
!define DLL64ID	`{CDC95B92-E27C-4745-A8C5-64A52A78855D}`
!define REGSVR	`$SYSDIR\regsvr32.exe`

;= MACROS
;= ################
!define DLL::Backup "!insertmacro _DLL::Backup"
!macro _DLL::Backup _GUID _FSR _SECTION _KEY _ERR1 _ERR2
	ClearErrors
	ReadRegStr $0 HKLM `SOFTWARE\Classes\CLSID\${_GUID}\InprocServer32` ""
	${IfNot} ${Errors}
		StrCmpS $Bit 64 0 +6
		StrCmp ${_FSR} /DISABLEFSR 0 +5
		${WriteRuntimeData} ${_SECTION} ${_KEY} `$0`
		ExecDos::Exec /TOSTACK /DISABLEFSR `"${REGSVR}" /s /u "$0"`
		Goto +2
		ExecDos::Exec /TOSTACK `"${REGSVR}" /s /u "$0"`
		Pop ${_ERR1}
		Pop ${_ERR2}
	${EndIf}
!macroend
!define DLL::Restore "!insertmacro _DLL::Restore"
!macro _DLL::Restore _FSR _SECTION _KEY _ERR1 _ERR2
	ClearErrors
	${ReadRuntimeData} $0 ${_SECTION} ${_KEY}
	${IfNot} ${Errors}
		StrCmpS $Bit 64 0 +4
		StrCmp ${_FSR} /DISABLEFSR 0 +3
		ExecDos::Exec /TOSTACK /DISABLEFSR `"${REGSVR}" /s "$0"`
		Goto +2
		ExecDos::Exec /TOSTACK `"${REGSVR}" /s "$0"`
		Pop ${_ERR1}
		Pop ${_ERR2}
	${EndIf}
!macroend
!define DLL::Register `!insertmacro _DLL::Register`
!macro _DLL::Register _DLL _FSR _ERR1 _ERR2
	StrCmpS $Bit 64 0 +4
	StrCmp ${_FSR} /DISABLEFSR 0 +3
	ExecDos::Exec /TOSTACK /DISABLEFSR `"${REGSVR}" /s "${_DLL}"`
	Goto +2
	ExecDos::Exec /TOSTACK `"${REGSVR}" /s "${_DLL}"`
	Pop ${_ERR1}
	Pop ${_ERR2}
!macroend
!define DLL::UnRegister `!insertmacro _DLL::UnRegister`
!macro _DLL::UnRegister _DLL _FSR _ERR1 _ERR2
	StrCmpS $Bit 64 0 +4
	StrCmp ${_FSR} /DISABLEFSR 0 +3
	ExecDos::Exec /TOSTACK /DISABLEFSR `"${REGSVR}" /s /u "${_DLL}"`
	Goto +2
	ExecDos::Exec /TOSTACK `"${REGSVR}" /s /u "${_DLL}"`
	Pop ${_ERR1}
	Pop ${_ERR2}
!macroend

;= CUSTOM 
;= ################
${SegmentFile}
${SegmentPre} ;= Unregister Local DLLs
	${DLL::Backup} `${DLL32ID}` "" RegisterDLL DLL32 $0 $1
	${If} $Bit == 64
		${DLL::Backup} `${DLL64ID}` /DISABLEFSR RegisterDLL DLL64 $0 $1
	${EndIf}
!macroend
${SegmentPrePrimary} ;= Register Portable DLLs
	${DLL::Register} `${DLL32}` "" $0 $1
	${If} $Bit == 64
		${DLL::Register} `${DLL64}` /DISABLEFSR $0 $1
	${EndIf}
!macroend
${SegmentPostPrimary} ;= Unregister Portable DLLs
	${DLL::UnRegister} `${DLL32}` "" $0 $1
	${If} $Bit == 64
		${DLL::UnRegister} `${DLL64}` /DISABLEFSR $0 $1
	${EndIf}
!macroend
${SegmentUnload} ;= Re-register Local DLLs
	${DLL::Restore} "" RegisterDLL DLL32 $0 $1
	${If} $Bit == 64
		${DLL::Restore} /DISABLEFSR RegisterDLL DLL64 $0 $1
	${EndIf}
!macroend
NOTE
The above example is just to show how to utilize these macros and is not meant to work for every situation. Some programs will have multiple registered DLLs for you to manage.
Other Macros
Discription
The following macros are alternatives which you may use for registering and unregistering DLL files but be sure to include the x64 plugin as these macros makes use of it's function.

RegisterDLL

;=# 
;= ${RegisterDLL} "SomeLibrary.dll"
;
!define RegisterDLL "!insertmacro _RegisterDLL"
!macro _RegisterDLL _DLL
	${If} ${RunningX64}
		${DisableX64FSRedirection}
		ExecWait '"$SYSDIR\regsvr32.exe" /s "${_DLL}"'
		${EnableX64FSRedirection}
	${Else}
		RegDLL "${_DLL}"
	${EndIf}
!macroend

UnregisterDLL

;=# 
;= ${UnregisterDLL} "SomeLibrary.dll"
;
!define UnregisterDLL "!insertmacro _UnregisterDLL"
!macro _UnregisterDLL _DLL
	${If} ${RunningX64}
		${DisableX64FSRedirection}
		ExecWait '"$SYSDIR\regsvr32.exe" /s /u "${_DLL}"'
		${EnableX64FSRedirection}
	${Else}
		UnRegDLL "${_DLL}"
	${EndIf}
!macroend