Introduction
Windows XP introduced a new COM activation model called Registration-Free COM activation. The Registration-Free COM activation is a registry replacement for COM components. The registry information will reside in a DotManifest file that can be stored in the same folder as the application itself.
This means that you don't have to have information into registry, information which is normally stored into HKEY_LOCAL_MACHINE, thus enabling regular user accounts to use COM DLLs without registering them into the system.
The problem with these DotManifest files is that they are pretty hard to write by hand. For managed assemblies there is Genman32 - A tool to generate Sxs manifest for managed assembly for Registration Free COM/.NET Interop, but there is no tool for native DLLs, this is why I've decided to write such a tool.
Background
The following MSDN article will give the proper background information: Registration-Free Activation of COM Components: A Walkthrough by Steve White and Leslie Muller. Also of great help is the MSDN Side-by-side Assemblies Reference.
A DotManifest file is a XML file — you probably have heard about these in conjunction with Common Controls version 6, or with Windows Vista elevation privileges and not to mention the (in)famous Microsoft.VC[8|9]0.CRT.manifest
and Microsoft.VC[8|9]0.MFC.manifest
files. These days every application has embedded a DotManifest file inside by the Manifest Tool as a resource type 24 (RT_MANIFEST). A typical DotManifest file looks like:
='1.0' ='UTF-8' ='yes'
<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level='asInvoker' uiAccess='false' />
</requestedPrivileges>
</security>
</trustInfo>
<dependency>
<dependentAssembly>
<assemblyIdentity type='win32' name='Microsoft.VC90.CRT' version='9.0.21022.8'
processorArchitecture='x86' publicKeyToken='1fc8b3b9a1e18e3b' />
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly>
<assemblyIdentity type='win32' name='Microsoft.VC90.MFC' version='9.0.21022.8'
processorArchitecture='x86' publicKeyToken='1fc8b3b9a1e18e3b' />
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly>
<assemblyIdentity type='win32' name='Microsoft.Windows.Common-Controls'
version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df'
language="'*'" />
</dependentAssembly>
</dependency>
</assembly>
The Spying
In order to get the the information about the COM registration process I had to "intercept" the following registry access functions:
- RegCreateKeyA
- RegCreateKeyW
- RegCreateKeyExA
- RegCreateKeyExW
- RegSetValueA
- RegSetValueW
- RegSetValueExA
- RegSetValueExW
- RegOpenKeyA
- RegOpenKeyW
- RegOpenKeyExA
- RegOpenKeyExW
- RegCloseKey
The interception was done by using the class CAPIHook
presented in Chapter 22: DLL Injection and API Hooking of the book Windows via C/C++, Fifth Edition written by Jeffrey Richter and Christophe Nasarre, Microsoft Press (c) 2008. One can use the Detours library from Microsoft Research instead of CAPIHook
. I've decided for the latter because of simpler deployment (just the executable, no signature DLL).
Setting up a CAPIHook
is being done like this
std::auto_ptr<CAPIHook> Interceptor::m_RegCreateKeyA;
...
if (!m_RegCreateKeyA.get())
{
m_RegCreateKeyA.reset(new CAPIHook("Advapi32.dll", "RegCreateKeyA",
(PROC)RegCreateKeyA));
}
The upper code will redirect RegCreateKeyA
through Interceptor::RegCreateKeyA
, the original function can be reached through m_RegCreateKeyA
. Interceptor::RegCreateKeyA
, like this:
LONG WINAPI Interceptor::RegCreateKeyA(HKEY hKey, LPCSTR lpSubKey, PHKEY phkResult)
{
if (m_doTrace)
{
PrintKeyStats(hKey, lpSubKey, __FUNCTIONW__);
}
LONG result = ERROR_ARENA_TRASHED;
try
{
result = ((PFNREGCREATEKEYA)(PROC)*m_RegCreateKeyA)(hKey, lpSubKey, phkResult);
}
catch (...)
{
}
if (result == ERROR_SUCCESS)
{
InsertSubkeyIntoUserKeyMap(hKey, *phkResult, lpSubKey, __FUNCTIONW__);
}
return result;
}
After all the information was acquired then the information is processed and written into DotManifest files. The manifest files are encoded as UTF-8. I would like to point fprintf
's ccs=<encoding> parameter which was used to save the manifest files as UTF-8.
void ManifestWriter::WriteToFile(const std::wstring& outputManifestFile)
{
FILE* file = _wfopen(outputManifestFile.c_str(), L"w, ccs=utf-8");
if (file)
{
fwrite(&*m_data.str().begin(), 1, m_data.str().size() * 2, file);
fclose(file);
}
}
In order to get all the information possible, first DllUnregisterServer
function is called, then DllRegisterServer
and at the end again DllUnregisterServer
. If you use regsvr42 on a system where you already have the COM DLL registered and still want to use it normally don't forget to run regsvr32 for that COM DLL.
Tool Usage
The basic usage is:
regsvr42 com.dll
which will generate a file named com.sxs.manifest. You can find out what interfaces and coclasses the COM DLL exports.
When used with a client application the usage is:
regsvr42 -client:client.exe com.dll
which will generate besides com.sxs.manifest another manifest file named client.exe.manifest. If client.exe already has a manifest file embedded, the contents of that manifest file are preserved into client.exe.manifest alongside with the reference to com.sxs assembly.
If you have more than one COM DLLs you want to use can use the tool in batch mode like:
regsvr42 -client:client.exe -batch:file_containing_com_dll_file_names
You can put all the COM DLLs inside one directory and there will be just one manifest file inside the directory named directory_name.manifest
regsvr42 -client:client.exe -dir:directory_with_com_dlls
If you have more than one directories with COM DLLs you can use the -batch function with all the names of the directories written in the batch file.
regsvr42 -client:client.exe -batch:file_containing_directory_names
DirectShow Filters
DirectShow filters are COM DLLs. They come in three flavours: source filters, transform filters and render filters.
The source filters add extra information into registry to support DirectShow's intelligent connect, this extra information cannot be stored into a DotManifest file. If your application relays on Intelligent Connect for a source filter, it will not work correctly.
There is one solution for this problem. For example instead of using IGraphBuilder::RenderFile
one should:
Add the source filter in to the DirectShow graph
Acquire the IFileSourceFilter
interface and call the Load
method
Acquire the output pin of the source filter and call IPin::Render
Test Application
I have made a test application which uses a source filter as described above. The test application will try to play an accPlus radio station by the use of Orban/Coding Technologies AAC/aacPlus Player Plugin
To test the application you need to have the Orban Plugin installed and the just run playradio.exe.
To test the Registration-Free COM mechanism first you have to unregister the Orban Plugin like:
regsvr32 /u "%ProgramFiles%\Orban\AAC-aacPlus Plugin\aacpParser.dll"
After that check to see that playradio.exe displays Class not registered. ErrorCode: 0x80040154.
Then run make_manifest.cmd, a convenience batch file, which does the DotManifest creation like regsvr42 -client:playradio.exe "%ProgramFiles%\Orban\AAC-aacPlus Plugin\aacpParser.dll". Two DotManifest files will be created:
aacpParser.sxs.manifest
="1.0" ="UTF-8" ="yes"
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
type="win32"
name="aacpParser.sxs"
version="1.0.0.0" />
<file name="C:\\Program Files\\Orban\\AAC-aacPlus Plugin\\aacpParser.dll">
<comClass
description="ORBAN-CT AAC/aacPlus Stream Parser"
clsid="{301F7BDA-B1F8-4453-82B2-0B9187DF3F3F}"
threadingModel="Both" />
<comClass
description="ORBAN-CT AAC/aacPlus Stream Parser About Page"
clsid="{CA920EED-F427-41B8-838F-33FCF47D5306}"
threadingModel="Both" />
</file>
</assembly>
playradio.exe.manifest
="1.0" ="UTF-8" ="yes"
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false">
</requestedExecutionLevel>
</requestedPrivileges>
</security>
</trustInfo>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="aacpParser.sxs"
version="1.0.0.0" />
</dependentAssembly>
</dependency>
</assembly>
File Hash
By using the command -hash the file sections will contain a SHA1 hash. To compute the hash I have used CodeProject's CSHA1 by Dominik Reichl. I have verified the validity of the hash by using OpenSSL like this openssl dgst -sha1<com.dll>.
The problem is that the Manifest Tool mt.exe doesn't think that the hash is valid! The command used was mt -manifest aacpParser.sxs.manifest -validate_file_hashes
The solution to this problem is to use the Manifest Tool to update the file hash, which renders the -hash function useless, like this: mt -manifest aacpParser.sxs.manifest -hashupdate If anybody knows how to adapt the CSHA1
to produce valid Manifest Tool SHA1 hashes please let me know.
Debugging
In case you encounter the following MessageBox:
This application has failed to start because the application configuration is incorrect. Reinstalling the application may fix this problem.
The Event Viewer - System category contains information about the loading errors of side by side assemblies.
History
2008-08-17 Initial release.