Here's something I didn't find out until today: You can use Visual Studio to easily create registrationless COM manifest files for you with just a couple of small steps. Registrationless COM lets you use COM component without them being registered in the registry. This means it's possible to deploy COM components along with another application using plain xcopy semantics. To be sure it's rarely quite that easy - you need to watch out for dependencies - but if you know you have COM components that are light weight and have no or known dependencies it's easy to get everything into a single folder and off you go.
Registrationless COM works via manifest files which carry the same name as the executable plus a .manifest extension (ie. yourapp.exe.manifest)
I'm going to use a Visual FoxPro COM object as an example and create a simple Windows Forms app that calls the component - without that component being registered. Let's take a walk down memory lane…
Create a COM Component
I start by creating a FoxPro COM component because that's what I know and am working with here in my legacy environment. You can use VB classic or C++ ATL object if that's more to your liking. Here's a real simple Fox one:
DEFINE CLASS SimpleServer as Session OLEPUBLIC
FUNCTION HelloWorld(lcName)
RETURN "Hello " + lcName
ENDDEFINE
Compile it into a DLL COM component with:
BUILD MTDLL simpleserver FROM simpleserver RECOMPILE
And to make sure it works test it quickly from Visual FoxPro:
server = CREATEOBJECT("simpleServer.simpleserver")
MESSAGEBOX( server.HelloWorld("Rick") )
Using Visual Studio to create a Manifest File for a COM Component
Next open Visual Studio and create a new executable project - a Console App or WinForms or WPF application will all do.
- Go to the References Node
- Select Add Reference
- Use the Browse tab and find your compiled DLL to import
- Next you'll see your assembly in the project.
- Right click on the reference and select Properties
- Click on the Isolated DropDown and select True
Compile and that's all there's to it. Visual Studio will create a App.exe.manifest file right alongside your application's EXE. The manifest file created looks like this:
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="App.exe"
version="1.0.0.0"
processorArchitecture="x86"
type="win32" />
<file name="simpleserver.DLL"
asmv2:size="27293">
<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>puq+ua20bbidGOWhPOxfquztBCU=dsig:DigestValue>
hash>
<typelib tlbid="{f10346e2-c9d9-47f7-81d1-74059cc15c3c}"
version="1.0"
helpdir=""
resourceid="0"
flags="HASDISKIMAGE" />
<comClass clsid="{af2c2811-0657-4264-a1f5-06d033a969ff}"
threadingModel="Apartment"
tlbid="{f10346e2-c9d9-47f7-81d1-74059cc15c3c}"
progid="simpleserver.SimpleServer"
description="simpleserver.SimpleServer" />
file>
assembly>
Now let's finish our super complex console app to test with:
using System;
using System.Collections.Generic;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static voidMain(string[] args)
{
Type type = Type.GetTypeFromProgID("simpleserver.simpleserver",true);
dynamic server = Activator.CreateInstance(type);
Console.WriteLine(server.HelloWorld("rick"));
Console.ReadLine();
}
}
}
Now run the Console Application… As expected that should work. And why not? The COM component is still registered, right? :-) Nothing tricky about that.
Let's unregister the COM component and then re-run and see what happens.
To be sure that the COM component no longer works, check it out with the same test you used earlier (ie. o = CREATEOBJECT("SimpleServer.SimpleServer") in your development environment or VBScript etc.).
Make sure you run the EXE and you don't re-compile the application or else Visual Studio will complain that it can't find the COM component in the registry while compiling. In fact now that we have our .manifest file you can remove the COM object from the project.
When you run run the EXE from Windows Explorer or a command prompt to avoid the recompile.
Watch out for embedded Manifest Files
Now recompile your .NET project and run it… and it will most likely fail!
The problem is that .NET applications by default embeds a manifest file into the compiled EXE application which results in the externally created manifest file being completely ignored. Only one manifest can be applied at a time and the compiled manifest takes precedency. Uh, thanks Visual Studio - not very helpful…
Note that if you use another development tool like Visual FoxPro to create your EXE this won't be an issue as long as the tool doesn't automatically add a manifest file. Creating a Visual FoxPro EXE for example will work immediately with the generated manifest file as is.
If you are using .NET and Visual Studio you have a couple of options of getting around this:
- Remove the embedded manifest file
- Copy the contents of the generated manifest file into a project manifest file and compile that in
To remove an embedded manifest in a Visual Studio project:
- Open the Project Properties (Alt-Enter on project node)
- Go down to Resources | Manifest and select | Create Application without a Manifest
You can now add use the external manifest file and it will actually be respected when the app runs.
The other option is to let Visual Studio create the manifest file on disk and then explicitly add the manifest file into the project. Notice on the dialog above I did this for app.exe.manifest and the manifest actually shows up in the list. If I select this file it will be compiled into the EXE and be used in lieu of any external files and that works as well.
Remove the simpleserver.dll reference so you can compile your code and run the application. Now it should work without COM registration of the component.
Personally I prefer external manifests because they can be modified after the fact - compiled manifests are evil in my mind because they are immutable - once they are there they can't be overriden or changed. So I prefer an external manifest. However, if you are absolutely sure nothing needs to change and you don't want anybody messing with your manifest, you can also embed it. The option to either is there.
Watch for Manifest Caching
While working trying to get this to work I ran into some problems at first. Specifically when it wasn't working at first (due to the embedded schema) I played with various different manifest layouts in different files etc.. There are a number of different ways to actually represent manifest files including offloading to separate folder (more on that later).
A few times I made deliberate errors in the schema file and I found that regardless of what I did once the app failed or worked no amount of changing of the manifest file would make it behave differently. It appears that Windows is caching the manifest data for a given EXE or DLL. It takes a restart or a recompile of either the EXE or the DLL to clear the caching. Recompile your servers in order to see manifest changes unless there's an outright failure of an invalid manifest file. If the app starts the manifest is being read and caches immediately.
This can be very confusing especially if you don't know that it's happening. I found myself always recompiling the exe after each run and before making any changes to the manifest file.
Don't forget about Runtimes of COM Objects
In the example I used above I used a Visual FoxPro COM component. Visual FoxPro is a runtime based environment so if I'm going to distribute an application that uses a FoxPro COM object the runtimes need to be distributed as well. The same is true of classic Visual Basic applications. Assuming that you don't know whether the runtimes are installed on the target machines make sure to install all the additional files in the EXE's directory alongside the COM DLL.
In the case of Visual FoxPro the target folder should contain:
- The EXE App.exe
- The Manifest file (unless it's compiled in) App.exe.manifest
- The COM object DLL (simpleserver.dll)
- Visual FoxPro Runtimes: VFP9t.dll (or VFP9r.dll for non-multithreaded dlls), vfp9rENU.dll, msvcr71.dll
All these files should be in the same folder.
Debugging Manifest load Errors
If you for some reason get your manifest loading wrong there are a couple of useful tools available - SxSTrace and SxSParse. These two tools can be a huge help in debugging manifest loading errors. Put the following into a batch file (SxS_Trace.bat for example):
sxstrace Trace -logfile:sxs.bin
sxstrace Parse -logfile:sxs.bin -outfile:sxs.txt
Then start the batch file before running your EXE. Make sure there's no caching happening as described in the previous section. For example, if I go into the manifest file and explicitly break the CLSID and/or ProgID I get a detailed report on where the EXE is looking for the manifest and what it's reading. Eventually the trace gives me an error like this:
INFO: Parsing Manifest File C:\wwapps\Conf\SideBySide\Code\app.EXE.
INFO: Manifest Definition Identity is App.exe,processorArchitecture="x86",type="win32",version="1.0.0.0".
ERROR: Line 13: The value {AAaf2c2811-0657-4264-a1f5-06d033a969ff} of attribute clsid in element comClass is invalid.
ERROR: Activation Context generation failed.
End Activation Context Generation.
pinpointing nicely where the error lies. Pay special attention to the various attributes - they have to match exactly in the different sections of the manifest file(s).
Multiple COM Objects
The manifest file that Visual Studio creates is actually quite more complex than is required for basic registrationless COM object invokation. The manifest file can be simplified a lot actually by stripping off various namespaces and removing the type library references altogether. Here's an example of a simplified manifest file that actually includes references to 2 COM servers:
xml version="1.0" encoding="utf-8"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1"
manifestVersion="1.0">
<assemblyIdentity name="App.exe"
version="1.0.0.0"
processorArchitecture="x86"
type="win32"
/>
<file name="simpleserver.DLL">
<comClass clsid="{af2c2811-0657-4264-a1f5-06d033a969ff}"
threadingModel="Apartment"
progid="simpleserver.SimpleServer"
description="simpleserver.SimpleServer" />
file>
<file name = "sidebysidedeploy.dll">
<comClass
clsid="{EF82B819-7963-4C36-9443-3978CD94F57C}"
progid="sidebysidedeploy.SidebysidedeployServer"
description="SidebySideDeploy Server"
threadingModel="apartment"
/>
file>
assembly>
Simple enough right?
Routing to separate Manifest Files and Folders
In the examples above all files ended up in the application's root folder - all the DLLs, support files and runtimes. Sometimes that's not so desirable and you can actually create separate manifest files. The easiest way to do this is to create a manifest file that 'routes' to another manifest file in a separate folder. Basically you create a new 'assembly identity' via a named id. You can then create a folder and another manifest with the id plus .manifest that points at the actual file.
In this example I create:
- App.exe.manifest
- A folder called App.deploy
- A manifest file in App.deploy
- All DLLs and runtimes in App.deploy
Let's start with that master manifest file. This file only holds a reference to another manifest file:
App.exe.manifest
xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1"
manifestVersion="1.0">
<assemblyIdentity name="App.exe"
version="1.0.0.0"
processorArchitecture="x86"
type="win32" />
<dependency>
<dependentAssembly>
<assemblyIdentity name="App.deploy"
version="1.0.0.0"
type="win32"
/>
dependentAssembly>
dependency>
assembly>
Note this file only contains a dependency to App.deploy which is another manifest id. I can then create App.deploy.manifest in the current folder or in an App.deploy folder. In this case I'll create App.deploy and in it copy the DLLs and support runtimes. I then create App.deploy.manifest.
App.deploy.manifest
xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1"
manifestVersion="1.0">
<assemblyIdentity
name="App.deploy"
type="win32"
version="1.0.0.0" />
<file name="simpleserver.DLL">
<comClass clsid="{af2c2811-0657-4264-a1f5-06d033a969ff}"
threadingModel="Apartment"
progid="simpleserver.SimpleServer"
description="simpleserver.SimpleServer" />
file>
<file name="sidebysidedeploy.dll">
<comClass
clsid="{EF82B819-7963-4C36-9443-3978CD94F57C}"
threadingModel="Apartment"
progid="sidebysidedeploy.SidebysidedeployServer"
description="SidebySideDeploy Server" />
file>
assembly>
In this manifest file I then host my COM DLLs and any support runtimes. This is quite useful if you have lots of DLLs you are referencing or if you need to have separate configuration and application files that are associated with the COM object. This way the operation of your main application and the COM objects it interacts with is somewhat separated.
You can see the two folders here:
Routing Manifests to different Folders
In theory registrationless COM should be pretty easy in painless - you've seen the configuration manifest files and it certainly doesn't look very complicated, right? But the devil's in the details. The ActivationContext API (SxS - side by side activation) is very intolerant of small errors in the XML or formatting of the keys, so be really careful when setting up components, especially if you are manually editing these files. If you do run into trouble SxsTrace/SxsParse are a huge help to track down the problems. And remember that if you do have problems that you'll need to recompile your EXEs or DLLs for the SxS APIs to refresh themselves properly.
All of this gets even more fun if you want to do registrationless COM inside of IIS :-) But I'll leave that for another blog post…
Other Posts you might also like