This article briefly explains different methods of using COM without registration of classes in HKEY_CLASSES_ROOT registry key. It gives basic information about COM implementation and oriented on the people with some level of familiarity of Windows programming. The article explains registration-free COM supported by Windows XP SP2 and also provides custom solution that could be used with older operating systems.
The article includes example of code that uses Microsoft Detours library which replaces keys in registry calls and allows easily tracing of your COM dependencies.
CLSID{GUID} Introduction
Sometimes you need to deploy application without administrative user rights. Or, for example, you need to create a demonstration DVD of your program with possibility to run it directly from a disk without installation. However if your application uses COM for communication between your components you need to perform registration of your COM objects. In the simplest way registration of COM object is just adding appropriate registry key to the HKEY_CLASSES_ROOTCLSID{CLASS_GUID} and sometimes also the registration of a type library HKEY_CLASSES_ROOTTYPELIB{TYPELIB_GUID}.
CLSID{GUID} registry key contains information about location of file implementing this class, class name (ProgID) and other optional information such as class version or threading model type.
TYPELIB{GUID} registry key contains information about location of type library file. Usually Type lib registration is required during development time only. However some kind of ActiveX use type library during run time. For example ATL based ActiveX use type library to provide calls via Invoke method of IDispatch interface.
The problem is that you can’t add keys to the HKEY_CLASSES_ROOT without administrative rights. Fortunately there are several ways how to use COM classes without registration.
Interception of Registry API Calls
When you creating class object it doesn’t matter how you do it or what kind of programming language you use. After all operation system will perform CoCreateInstance method to create appropriate object. This method look into the registry key (HKEY_CLASSES_ROOTCLSID{CLASS_GUID}) (and subkeys) to define the file containing that class. CoCreateInstance uses RegOpenKeyEx, RegQueryValueEx and RegCloseKey for that purposes. So you can intercept those 3 functions to implement your own virtual registry. This will make CoCreateInstance believe it look to the real registry.
However implementing a virtual registry is a quite complex task. As simple alternative we can put all necessary values to the HKEY_CURRENT_USER instead of HKEY_CLASSES_ROOT during first application call. This will allow us intercept RegOpenKeyEx only and redirect queries from HKEY_CLASSES_ROOT to HKEY_CURRENT_USER.
Here is the example of code that redirects all RegOpenKeyEx calls for specified list of class keys and subkeys. This code uses Microsoft Detours library to perform interception of function call.
// Saving original pointer of RegOpenKeyEx static LONG (WINAPI * TrueRegOpenKeyEx)(HKEY hKey, LPCWSTR lpSubKey, DWORD ulOptions, REGSAM samDesired, PHKEY phkResult) = RegOpenKeyEx; // Wrapper of RegOpenKeyEx LONG WINAPI MyRegOpenKeyEx(HKEY hKey, LPCWSTR lpSubKey, DWORD ulOptions, REGSAM samDesired, PHKEY phkResult) { const WCHAR *rk[] = { // The list of objects GUIDs to redirect L"CLSID{F025BF4E-4081-4C02-86E6-6602AEE846F6}", L"CLSID{21E12ADF-C657-40dd-8DC9-493F5196739E}", L"CLSID{C7BFE922-B065-3D41-8135-90BB2731F434}", L"CLSID{D9D1E30A-F4FC-31E6-9900-169754AFBA60}", L"CLSID{E12CC83A-5D20-318B-AC8F-750BB2B21ACA}", L"CLSID{E4EB2161-425C-3B31-ADFE-63198995CB1D}", L"CLSID{E5A3FAF7-6F1A-3D04-B5E8-935B9C954075}", L"CLSID{F0A50734-DD32-3CA7-82A6-F00A0143207B}", L"CLSID{F26A2E5F-F503-3243-880D-4B235626A69A}", L"CLSID{FFAEE031-04CC-3D14-A893-855F23B3599B}" }; for (int i = 0; i < sizeof(rk) / sizeof(WCHAR*); i++) { if (lpSubKey && wcsnicmp(rk[i], lpSubKey, wcslen(rk[i])) == 0) { return TrueRegOpenKeyEx(HKEY_CURRENT_USER, lpSubKey, ulOptions, samDesired, phkResult); } } return TrueRegOpenKeyEx(hKey, lpSubKey, ulOptions, samDesired, phkResult); } . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . // Initialization // Must be called during application startup (before creating of any class objects) DisableThreadLibraryCalls(hModule); DetourTransactionBegin(); DetourAttach(&(PVOID&)TrueRegOpenKeyEx, MyRegOpenKeyEx); DetourUpdateThread(GetCurrentThread()); DetourTransactionCommit();
Microsoft Registration-Free Com Mechanism
Starting from Windows XP (SP2) you can use Microsoft mechanism of COM access without registration. The idea of Microsoft solution is putting COM registration information to the external PE manifest instead of a registry.
This means we should create (or modify existent) external manifests for main executable and COM servers you are going access without registration. In the main executable manifest you should refer to PE files with COM classes (COM Servers) and put information about them to the manifests of files with COM classes. The advantage of such solution is that unlike the previous one, this doesn’t require recompilation and can be used if you haven’t the source code of modules.
I’m not going to explain the syntax of the manifest files in details here. You can find detailed syntax explanation in the MSDN library. For the moment please consider focusing on the few important things which can cause troubles. Some of them weren’t documented at the moment when this article was written.
Let’s see following example.
We have one executable (MyApp.exe), one native DLL with 2 COM classes (MyNativeLib.dll) and one managed assembly also with 2 classes we want to access via COM.
The first step is to create manifest for MyApp.exe. The name of manifest file must be MyApp.exe.manifest.
[MyApp.exe.manifest] <?xml version="1.0" encoding="utf-8" standalone="yes"?> <assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1"> <assemblyIdentity type="win32" name="MyApp.exe" version="1.0.0.0" /> <dependency> <dependentAssembly> <assemblyIdentity type="win32" name="MyNativeLib.dll" version="4.2.0.0" /> </dependentAssembly> </dependency> <dependency> <dependentAssembly> <assemblyIdentity name="MyManagedAssembly" version="1.0.0.11" publicKeyToken="1d8d957c9e4100ad" /> </dependentAssembly> </dependency> </assembly>
As you can see there are two dependency tags in the following example. Each of them refers to the appropriate assembly. Pay attention that syntax of reference to the managed and native dll is different. For example the assembly identity name of managed dll must be equal to the assembly name (not the name of file). At the same time assembly identity name of native dll could be any unique string.
Now let’s create a manifest for the native DLL.
[MyNativeLib.dll.manifest] <?xml version="1.0" encoding="utf-8" standalone="yes"?> <assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1"> <assemblyIdentity type="win32" name=" MyNativeLib.dll" version="4.2.0.0" /> <file name="MyNativeLib.dll"> <typelib tlbid="{69ECBBD3-5C2A-4A84-ABEC-23937DBF1B54}" version="1.4" helpdir=""/> <comClass progid="MyNativeClass1" clsid="{21E12ADF-C657-40DD-8DC9-493F5196739E}" threadingModel="Both" /> <comClass progid="MyNativeClass2" clsid="{2208A749-4832-4E39-9E12-D8A2619EBB9F}" threadingModel="Both" /> </file> </assembly>
And manifest for the managed DLL.
[MyManagedAssembly.manifest] <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1"> <assemblyIdentity name="MyManagedAssembly" version="1.0.0.11" publicKeyToken="1d8d957c9e4100ad" /> <clrClass clsid="{058BE4D6-F73C-3F31-ABFD-558744729163}" progid="MyAssembly.MyClass1" threadingModel="Both" name="MyClass1"></clrClass> <clrClass clsid="{058BE4D6-F73C-3F31-ABFD-558744729164}" progid="MyAssembly.MyClass2" threadingModel="Both" name="MyClass2"></clrClass> </assembly>
With the native DLL everything is quite clear. As was mentioned before assembly identity name of native dll can be any string. So if you call it “blabla.bl.a” for example. You should name the manifest file: “blabla.bl.a.manifest”. The “file” tag should refer to the correct DLL. Thereby when PE loader performs the loading of our application it also loads “MyApp.exe.manifest“, finds reference to “blabla.bl.a” assembly there, reads “blabla.bl.a.manifest”, finds “file” tag and saves all “comClass”, “typelib” tags and the file name somewhere to the memory. Then when application calls CoCreateInstance or CLSIDFromProgID operation system will read appropriate values from the memory instead the registry. As you could see, solution looks quite simple and normally works predictable after first try.
For unmanaged library you don’t need to add a file tag into the manifest. Actually you can but it looks like operation system is successfully ignoring it. And here is the first interesting thing you’ll probably face. The operation system is always looking for a dll first. Only if a dll file wasn’t found it tries finding the manifest. So if the assembly name and the assembly file name (without extension) are the same (which is very common in .NET) OS will never load a manifest. That will happen in our case if we’ll put MyManagedAssembly.manifest close to MyManagedAssembly.dll.
As always thanks to Mark Russinovich for the Process Explorer. What I did is removed managed dll and start application with Process Explorer running. Of course application failed with “File Not Found” exception at the moment it was tried to load the dll. But I saw the place OS have tried to load it from. It is AssemblyNameAssemblyName.dll. So in our case the files structure must be following:
MyApp.exe MyApp.exe.manifest MyNativeLib.dll MyNativeLib.dll.manifest MyManagedAssemblyMyManagedAssembly.dll MyManagedAssembly.manifest
Creating manifest files
In our example we have assemblies with just 2 classes each. It wasn’t difficult to create manifests manually in the notepad. But in real application you probably have assemblies with hundreds of classes.
Manifest Tool
Microsoft has a command line based Manifest Tool – mt.exe. It allows creating manifests based on already compiled dlls. There is no adequate documentation about these tools in the SDK so there is the example how to use it:
[Managed] mt.exe -managedassemblyname:MyManagedAssembly.dll -nodependency -out:MyManagedAssembly.manifest [Native] mt.exe -dll:MyNativeLib.dll -tlb:MyNativeLib.dll -out:MyNativeLib.dll.manifest
Unfortunately the XML generated by mt.exe is not a correct manifest file. It very looks like Microsoft didn’t plan to publish this utility at all and it got to the SDK accidentally.
Manifest tool adds two extra tags <runtime> and <mvid> to the manifest for managed dll. Fortunately it’s easy to fix. You could remove those tags in the notepad to make a correct manifest. Or if you want to generate a manifest automatically during application build you always could write a simple parser converting the XML.
For native dll Manifest Tool doesn’t generate progid attributes for the comclass tag. So CLSIDFromProgID will not work for such classes. Of course you could solve this if you write own tool that will:
- Parse XML and get CLSID for every tag
- Finds appropriate CLSID in the registry (HKEY_CLASSES_ROOTCLSID).
- Get ProgID from a registry and put in the XML.
Of course DLL must be registered while your tool is working.
Custom Solution
There are can be situations when native COM server doesn’t have a type library at all. For example ActiveX which has custom implementation of DllRegisterServer method.
In that case we can intercept registry API calls from our client application. Again we need to intercept only RegOpenKeyEx function. Injected function should collect all successful opened keys in HKEY_CLASSES_ROOTCLSID, retrieve information progid and save it to the intermediate storage for example into the std::map. At the end application should save collected information to the XML file or files.
The additional advantage of such method is that we will get manifest with the only classes which are already used by client application.
The following example works only for managed dlls save all collected classes to the one file. But you could modify and make this code generating separate manifest.
struct ComClass { string progid; string guid; string desc; ComClass& operator=(const ComClass &f) { progid = f.progid; guid = f.guid; desc = f.desc; return *this; } ComClass() {} ComClass(const ComClass &f) { *this = f; } }; typedef map<string, map<string, ComClass> > Files; Files files; static LONG (WINAPI * TrueRegOpenKeyEx)(HKEY hKey, LPCWSTR lpSubKey, DWORD ulOptions, REGSAM samDesired, PHKEY phkResult) = RegOpenKeyEx; LONG WINAPI MyRegOpenKeyEx(HKEY hKey, LPCWSTR lpSubKey, DWORD ulOptions, REGSAM samDesired, PHKEY phkResult) { long res = TrueRegOpenKeyEx(hKey,lpSubKey,ulOptions,samDesired,phkResult); if (phkResult && lpSubKey && wcsnicmp(L"CLSID",lpSubKey,6) == 0) { char t[4096]; long len = 4096; WCHAR tw[4096]; t[36] = 0; WideCharToMultiByte(CP_UTF8,0,lpSubKey+7,36,t,4096,NULL,NULL); string guid = t; string file; string desc; string progid; len = 4096; RegQueryValueW(*phkResult,L"",tw,&len); WideCharToMultiByte(CP_UTF8,0,tw,-1,t,4096,NULL,NULL); desc = t; HKEY hKey; if (TrueRegOpenKeyEx(*phkResult,L"InprocServer32",0,KEY_READ,&hKey) == ERROR_SUCCESS) { len = 4096; RegQueryValueW(hKey,L"",tw,&len); RegCloseKey(hKey); WideCharToMultiByte(CP_UTF8,0,tw,-1,t,4096,NULL,NULL); file = t; } if (TrueRegOpenKeyEx(*phkResult,L"ProgID",0,KEY_READ,&hKey) == ERROR_SUCCESS) { len = 4096; RegQueryValueW(hKey,L"",tw,&len); RegCloseKey(hKey); WideCharToMultiByte(CP_UTF8,0,tw,-1,t,4096,NULL,NULL); progid = t; } if (guid.length() > 0 && file.length() > 0 && desc.length() > 0 && progid.length() > 0) { ComClass mf; mf.desc = desc; mf.progid = progid; mf.guid = guid; files[file][guid] = mf; } } return res; } void WriteXml() { FILE *F = _wfopen(L"MyManifests.xml",L"wt"); if (F) { for (Files::const_iterator f = files.begin(); f != files.end(); ++f) { char t[4096]; fwprintf(F," n",f->first.c_str()); for (map<string,ComClass>::const_iterator c = f->second.begin(); c != f->second.end(); ++c) { fwprintf(F," n", c->second.progid.c_str(), c->second.guid.c_str(), c->second.desc.c_str()); } fwprintf(,F" nn"); fwprintf(F,L"%s",str); fclose(F); } } }
If you need to create manifests automatically during client application build I suggest you to do following:
- Define injection function (MyRegOpenKeyEx) that will collect requests to the used class objects.
- Define function (WriteXML) that will save collected information to manifests.
- Add “manifest mode” to your client application. For example with special command line switch. In that mode application should:
- Work in silent mode.
- Inject MyRegOpenKeyEx method.
- Create all COM classes it could use in all the cases.
- Call WriteXML method.
- Make Post-Build command line script of the client application project running application in a manifest mode.
As you can see there are multiple solutions how to make your application works without COM registration. If you don’t need to support old operating system I suggest you to use manifests. Microsoft has already done most of the things for us. But nothing is perfect and at the moment you still need reinvent the wheel and/or do manual job to create manifests. I do hope this will be fixed in the new version of Visual Studio.
Mike Makarov,
DevelopEx CTO