DLL Hell! How use Registration Free COM with C++ – The Practical Way
[stextbox id=”info” caption=”Comment or Leave a Message”]
Please Comment or Leave a Message if this is what you are looking for or if you have any question/comment/suggestion/request.
[/stextbox]
If you are reading this post it is probably because you have some trouble trying to have your application to run on a system where some COMs dependencies don’t seem to be happily resolved, or because you can’t afford to register the COMs at all (policy?)… or if you don’t have any COM problem at all, well… keep reading. The DLL Hell is always behind the corner.
Because the problem is quite annoying when happens, I provide at least four solutions so that you can choose the one you find more comfortable to use :). Let me know which one you prefer and if you know other alternative ways to do it as painlessly as possible.
The Problem
Imagine to have a simple application that references a COM object. You try to build your COM and then your app, and eventually you try to run it on your machine and… it works! Good! So you happily go and copy your App and the DLL of the COM in a single folder on a second machine and… BUM! Your App crashes without hope.
So, what’s going on?
The problem relies on the fact that when you use a COM DLL (with Early Binding), you are somehow coupling your App with a precise version of your COM library. This means that if one day you or someone else upgrades the COM DLL, then the App will still continue to work using the original COM it was binded to. What really happens is that your App will not simply try to locate the DLL (using only its name) in the current folder and then in the PATH environment variable, but will actually use a GUID (the CLSID) to locate the COM DLL in the Windows Registry and from there locate the file in the file system. GUID stands for Globally Unique IDentifier (while CLSID is the CLaSs IDentifier) and is automatically generated for you when you create the COM object and automatically used when you reference your COM from your App. If you want to see how to use a COM you can have a look at my other post ([intlink id=”114″ type=”post”]Create a .NET COM and use it in Unmanaged C++[/intlink]).
This GUID is then the only thing that the App will use to locate the COM DLL, and in order to make this work you have to “Register” your COM classes as we already know.
So, what if you don’t want to register it?
Sample Scenario
Let’s assume that we have a couple of COM DLLs with one sample Object each and both with a simple (different) method defined. In my example I have these:
- MyPippoCOM COM with a SillyAdder class (CoClass)
- MyOtherCOM COM with a SmarterMultiplier CoClass
- TestApp exe that uses the two previous COMs using Early Bindings
In TestApp I instantiate the objects using the ProgID.
The Tool
Fortunately Microsoft came up with a (almost) simple solution.
The trick is create a *special* file that will tell to our App how to behave and where to look for what it needs. This file is the “Manifest”.
As a brief description let’s just say that this is a simple XML file and in there we can specify where are the DLLs that we want to provide to our app, bypassing the normal searching behaviour.
The Problem of the Tool
The problem here is that writing what you need here is not always (actually almost never) easy. In fact, in order to describe the COM that you App will have to use, you must “describe” it, specifying all the GUIDs, ProgIds (if you need it), and other nasty details that are generally (and fortunately) auto-generated for us when we Create and use a COM in a normal/standard way… So why do we have to suffer now? Fortunately we don’t, we are safe this time as well :P.
The Basic Solution – Understand What’s Going On
Based on Microsoft Documentation, the way to create a manifest for your DLL is to use the Tool mt.exe (this is the Documentation).
With this tool you can generate the manifest that you will have to use with your App if you want to achieve our goal.
For example, considering our MyPippoCOM.dll DLL, we could do something like this (from the command prompt, in the folder where the dll and tlb files are stored):
1 |
mt.exe -tlb:MyPippoCOM.tlb -dll:MyPippoCOM.dll -out:mySample.manifest |
This will generate a file named “mySample.manifest” that, in my case, will contain something like this:
1 2 3 4 5 6 7 8 9 10 11 |
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <file name="MyPippoCOM.dll" hashalg="SHA1"> <comClass clsid="{02FF2DBC-6621-436B-8160-B081C380767F}" tlbid="{65CC16D3-F0F9-4C32-BA26-39C2882A1526}" description="SillyAdder Class"> </comClass> <typelib tlbid="{65CC16D3-F0F9-4C32-BA26-39C2882A1526}" version="1.0" helpdir="" flags="HASDISKIMAGE"> </typelib> </file> <comInterfaceExternalProxyStub name="ISillyAdder" iid="{82DD2AED-0D32-48B7-957F-06F41433B813}" tlbid="{65CC16D3-F0F9-4C32-BA26-39C2882A1526}" proxyStubClsid32="{00020424-0000-0000-C000-000000000046}"> </comInterfaceExternalProxyStub> </assembly> |
At this point you could do the same thing for the other DLL and then (only after), create the manifest for your app.
This is because with this tool you can only specify one dll and tlb when you generate a manifest and consequently you cannot create a manifest with the data for both DLLs in one go. Fortunately though this tool allows you to merge multiple manifests! So you could use this as final step toward the creation of the Application manifest. I said “toward the creation” because unfortunately the manifest that you will have at the end of this process will be incomplete and could contain some errors as well if you didn’t follow exactly my example and you specified the dll and tlb file using an absolute/relative path instead. I’ll explain later how fix this. For now le’s see how could you merge the two manifests:
1 |
mt.exe -manifest MyPippoCOMmySample.manifest MyOtherCOMmySample.manifest -out:merged.manifest |
The result will be a file named “merged.manifest” that in my case will contain this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <file name="MyPippoCOM.dll" hashalg="SHA1"> <comClass clsid="{02FF2DBC-6621-436B-8160-B081C380767F}" tlbid="{65CC16D3-F0F9-4C32-BA26-39C2882A1526}" description="SillyAdder Class"> </comClass> <typelib tlbid="{65CC16D3-F0F9-4C32-BA26-39C2882A1526}" version="1.0" helpdir="" flags="HASDISKIMAGE"> </typelib> </file> <file name="MyOtherCOM.dll" hashalg="SHA1"> <comClass clsid="{2E9335B6-A03F-4897-AC9D-31150FD7E246}" tlbid="{51B323D0-35DC-40AB-BC18-97569336A061}" description="SmarterMultiplier Class"> </comClass> <typelib tlbid="{51B323D0-35DC-40AB-BC18-97569336A061}" version="1.0" helpdir="" flags="HASDISKIMAGE"> </typelib> </file> <comInterfaceExternalProxyStub name="ISillyAdder" iid="{82DD2AED-0D32-48B7-957F-06F41433B813}" tlbid="{65CC16D3-F0F9-4C32-BA26-39C2882A1526}" proxyStubClsid32="{00020424-0000-0000-C000-000000000046}"> </comInterfaceExternalProxyStub> <comInterfaceExternalProxyStub name="ISmarterMultiplier" iid="{7E87E208-FA5E-4CFB-B057-7F7035176736}" tlbid="{51B323D0-35DC-40AB-BC18-97569336A061}" proxyStubClsid32="{00020424-0000-0000-C000-000000000046}"> </comInterfaceExternalProxyStub> </assembly> |
As it stands you cannot use it directly. You need to rename it, fix a couple of things in it, adding some missing informations and you need to build the TestApp without embedding its manifest.
So, first things first. Go back in VS2005 and build our TestApp changing one setting. Open the project’s properties, expand the section “Manifest Tool” and enter in the “Input and Output” entry. In the option screen on the right set “Embed Manifest” to “No”.
Now you can build the project again and this time a file TestApp.exe.manifest (in my case) will sit next to the executable.
Now open it. It should look like this (the dependentAssembly section will include the proper assembly based on which type of build you selected, debug or release):
1 2 3 4 5 6 7 8 |
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <dependency> <dependentAssembly> <assemblyIdentity type="win32" name="Microsoft.VC80.CRT" version="8.0.50727.4053" processorArchitecture="x86" publicKeyToken="1fc8b3b9a1e18e3b"></assemblyIdentity> </dependentAssembly> </dependency> </assembly> |
Now it is just a question to grab our previous merged manifest (from the two COMs) and copy all the content that appears inside the “assembly” tags into the TestApp.exe.manifest, just before the assembly closing tag. In my case this will be the result of this step:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <dependency> <dependentAssembly> <assemblyIdentity type="win32" name="Microsoft.VC80.CRT" version="8.0.50727.4053" processorArchitecture="x86" publicKeyToken="1fc8b3b9a1e18e3b" /> </dependentAssembly> </dependency> <file name="MyPippoCOM.dll" hashalg="SHA1"> <comClass clsid="{02FF2DBC-6621-436B-8160-B081C380767F}" tlbid="{65CC16D3-F0F9-4C32-BA26-39C2882A1526}" description="SillyAdder Class"> </comClass> <typelib tlbid="{65CC16D3-F0F9-4C32-BA26-39C2882A1526}" version="1.0" helpdir="" flags="HASDISKIMAGE"> </typelib> </file> <file name="MyOtherCOM.dll" hashalg="SHA1"> <comClass clsid="{2E9335B6-A03F-4897-AC9D-31150FD7E246}" tlbid="{51B323D0-35DC-40AB-BC18-97569336A061}" description="SmarterMultiplier Class"> </comClass> <typelib tlbid="{51B323D0-35DC-40AB-BC18-97569336A061}" version="1.0" helpdir="" flags="HASDISKIMAGE"> </typelib> </file> <comInterfaceExternalProxyStub name="ISillyAdder" iid="{82DD2AED-0D32-48B7-957F-06F41433B813}" tlbid="{65CC16D3-F0F9-4C32-BA26-39C2882A1526}" proxyStubClsid32="{00020424-0000-0000-C000-000000000046}"> </comInterfaceExternalProxyStub> <comInterfaceExternalProxyStub name="ISmarterMultiplier" iid="{7E87E208-FA5E-4CFB-B057-7F7035176736}" tlbid="{51B323D0-35DC-40AB-BC18-97569336A061}" proxyStubClsid32="{00020424-0000-0000-C000-000000000046}"> </comInterfaceExternalProxyStub> </assembly> |
Ok! Almost there.
The final step is to add something that mt.exe doesn’t add for us but that I like to have, the ProgID attribute. You have to do this by hand unfortunately. Just add this attribute to any comClass tag. The final result in my case is this (I optimised some tags a little bit as well):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <dependency> <dependentAssembly> <assemblyIdentity type="win32" name="Microsoft.VC80.CRT" version="8.0.50727.4053" processorArchitecture="x86" publicKeyToken="1fc8b3b9a1e18e3b" /> </dependentAssembly> </dependency> <file name="MyPippoCOM.dll" hashalg="SHA1"> <comClass clsid="{02FF2DBC-6621-436B-8160-B081C380767F}" tlbid="{65CC16D3-F0F9-4C32-BA26-39C2882A1526}" progid="MyPippoCOM.SillyAdder" description="SillyAdder Class" /> <typelib tlbid="{65CC16D3-F0F9-4C32-BA26-39C2882A1526}" version="1.0" helpdir="" flags="HASDISKIMAGE" /> </file> <file name="MyOtherCOM.dll" hashalg="SHA1"> <comClass clsid="{2E9335B6-A03F-4897-AC9D-31150FD7E246}" tlbid="{51B323D0-35DC-40AB-BC18-97569336A061}" description="SmarterMultiplier Class" progid="MyOtherCOM.SmarterMultiplier" /> <typelib tlbid="{51B323D0-35DC-40AB-BC18-97569336A061}" version="1.0" helpdir="" flags="HASDISKIMAGE" /> </file> <comInterfaceExternalProxyStub name="ISillyAdder" iid="{82DD2AED-0D32-48B7-957F-06F41433B813}" tlbid="{65CC16D3-F0F9-4C32-BA26-39C2882A1526}" proxyStubClsid32="{00020424-0000-0000-C000-000000000046}" /> <comInterfaceExternalProxyStub name="ISmarterMultiplier" iid="{7E87E208-FA5E-4CFB-B057-7F7035176736}" tlbid="{51B323D0-35DC-40AB-BC18-97569336A061}" proxyStubClsid32="{00020424-0000-0000-C000-000000000046}" /> </assembly> |
Note that the ProgID should actually contain the version number as well (e.g. progid=”MyPippoCOM.SillyAdder.1″), but the other version does work if you don’t specify this number when creating the object in your code as in:
1 |
ppSillyAdder = new MyPippoCOMLib::ISillyAdderPtr("MyPippoCOM.SillyAdder"); |
That’s all for this first long manual version.
There is a way to skip some steps and do it slightly more automatically.
The Semi-Practical Solution – The Slightly More Automatic Way
A way that I found helpful to adopt is to make usage of the post-build steps feature.
To keep it short and simple, what you can do is edit the post-build event for both of your COMs projects, adding somethig like this:
1 |
mt.exe -tlb:$(OutDir)MyPippoCOM.tlb -dll:$(OutDir)MyPippoCOM.dll -out:$(OutDir)manifest.manifest |
(and similarly for the other COM).
This will generate automatically a manifest named “manifest.manifest” (nice name uh? :P) in the project output folder.
Then edit the TestApp project’s properties, writing in “Manifest Tool | Input Output | Additional Manifest Files” the location of any manifest that you want to merge.
Those two settings will essentially automate the creation and merge of the manifest that we previously did manually!
You will have to do the final fixes anyway. Maybe you can try to automate this as well :).
The Practical Solution – The Fast Way
I found a way to cheat and generate the manifest with even less work. It is sort of cheating, but it is fast and works! :).
Instead of opening Visual Studio 2005, open VS2010 and create a new project. Just any project, a console app for example.
Now right click the References tab in the solution explorer and select “Add Reference…”.
In the window that will pop-up select “Browse” and navigate where the first COM DLL is located and add it.
Repeat the same process for all the other COMs.
When the References list is completed, expand it and select all the COMs that you just added.
Go in the “Properties” window of the selected references entries and change the value of “Isolated” to True.
Job Done!
Now when you will build the project a .manifest file will be automatically generated and it will contain the correct XML for both COMs.
mine looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<?xml version="1.0" encoding="utf-8"?> <assembly xsi:schemaLocation="urn:schemas-microsoft-com:asm.v1 assembly.adaptive.xsd" manifestVersion="1.0" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" xmlns:dsig="http://www.w3.org/2000/09/xmldsig#" xmlns:co.v1="urn:schemas-microsoft-com:clickonce.v1" xmlns:co.v2="urn:schemas-microsoft-com:clickonce.v2" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <assemblyIdentity name="ManifestGenerator.exe" version="1.0.0.0" processorArchitecture="x86" type="win32" /> <file name="MyOtherCOM.dll" asmv2:size="24576"> <hash xmlns="urn:schemas-microsoft-com:asm.v2"> <dsig:Transforms> <dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" /> </dsig:Transforms> <dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /> <dsig:DigestValue>RnGnkpsWfORg9KcGAkeo5jSedRo=</dsig:DigestValue> </hash> <typelib tlbid="{51b323d0-35dc-40ab-bc18-97569336a061}" version="1.0" helpdir="" resourceid="0" flags="HASDISKIMAGE" /> <comClass clsid="{2e9335b6-a03f-4897-ac9d-31150fd7e246}" threadingModel="Apartment" tlbid="{51b323d0-35dc-40ab-bc18-97569336a061}" progid="MyOtherCOM.SmarterMultiplier.1" description="SmarterMultiplier Class" /> </file> <file name="MyPippoCOM.dll" asmv2:size="24576"> <hash xmlns="urn:schemas-microsoft-com:asm.v2"> <dsig:Transforms> <dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" /> </dsig:Transforms> <dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /> <dsig:DigestValue>VaZjBh9dIRXm3/6NueTBb3TYO3A=</dsig:DigestValue> </hash> <typelib tlbid="{65cc16d3-f0f9-4c32-ba26-39c2882a1526}" version="1.0" helpdir="" resourceid="0" flags="HASDISKIMAGE" /> <comClass clsid="{02ff2dbc-6621-436b-8160-b081c380767f}" threadingModel="Apartment" tlbid="{65cc16d3-f0f9-4c32-ba26-39c2882a1526}" progid="MyPippoCOM.SillyAdder.1" description="SillyAdder Class" /> </file> </assembly> |
Notice that it contains the ProgID attribute with the version number (“.1″) at the end of the string (see: progid=”MyPippoCOM.SillyAdder.1”). You will have to remove it if you don’t specify it in your TestApp application.
Apart from this, you can directly use this as input manifest for the TestApp saving even more time! :) (you can consider to change the assemblyIdentity attribute if you do this).
NOTE: In order to apply this solution the COMs must be registered on the developer machine. This must be said just in case you did some previous steps and you unregistered them and you didn’t rebuild the projects ever after.
The Speedy Solution – The Last Tip!
There is a final tip to avoid even more typing. The only thing to prepare is the merged manifest in the way you prefer and use this one as Additional Manifest File in the Input and Output section of the Manifest Tool in the properties of the C++ TestApp project. Then, instead of setting the Embed Manifest to “No”, Leave it to “Yes”.
This will generate the application exe without any needed manifest and this is ready to be executed side-by-side with the COM DLLs.
No more editing, no more problems :).
Note about VB6 COMs
If you have VB6 COMs the previous method will still work really well (even with a correct ProgID), but if you want to explore even alternative ways, there is an interesting tool that you can try to use to generate the manifest for each specific COM.
You can find the tool here: Make My Manifest
What it does is essentially create a manifest for any VB6 projects (not only COMs) generating a complete manifest with all the project’s COM dependencies.
So this is more exhaustive than our single C++ COM manifest and this means that if you have multiple VB6 COMs that uses others COMs, then the manifests could contain common elements and thus the merge should take care of this. But for the rest it is a quite nice tool to use, so give it a try.
Conclusion
The DLL Hell is not a random name and what I tried to cover in this post is just the tip of the iceberg.
I wanted to write this because I found quite difficult to gather the practical information that I ended up discovering personally through a long series of frustrating experiments in order to properly understand and faultlessly solve the main problem.
I hope that this helped you and you found it interesting.
Let me know if you discovered other ways to solve it or if you have any suggestion/question/information that you want to share ;).
You advice here was invaluable to me! I have been trying to build some reg-free COM components on a build server without using regsvr32, and part of that means creating manifests manually. I had no idea how to do it but your blog entry explained it all very well.
Great article very well explained !
A pleasure to read and follow !
you don’t actually need to edit the file to add the default ProgID (the one without the version number at the end) you just need to add a post built step which applies a transform to your manifest and change it
from
To
MyPippoCOM.SillyAdder