Registration-Free COM with .NET

Registration-free COM interop activates a component without using the Windows registry to store assembly information. Instead of registering a component on a computer during deployment, you create Win32-style manifest files at design time that contain information about binding and activation. These manifest files, rather than registry keys, direct the activation of an object.

Using registration-free activation for your assemblies instead of registering them during deployment offers two advantages:

  1. You can control which DLL version is activated when more than one version is installed on a computer.

  2. It simplifies deployment process of the application by allowing replacing DLLs without accessing and editing registry

Simple .NET COM component

For .NET component I will use simple .NET class library project. Let's name it SxsNETCom. To make use of COM we first need to create interface for our component and then actual component/class which will implement this interface and mark it with appropriate attributes. My COM component will simple read XML file and return content in form of string. Here is this interface definition.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace SxsNETCom
{
    [ComVisible(true)]
    [Guid("C38897BF-EA67-46F0-A342-A56C515563C2")]
    public interface IReadXml
    {
        string ReadXmlFile(string filePath);
    }
}

Important to notice here "ComVisible" attribute which tells this interface should be exported and Guid attribute which uniquely defines our interface. This Guid will be later used by clients to actually find our component. Any Guid will do and we can simple generate it from withing Visual Studio under Tools, Create GUID sub menu using Registry format. Next step is to define our COM component an actual class and implement our interface. Here is code for it. Very simple, we take file path as input parameter, read xml file into XmlDocument and return string content of XmlDocument.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

using System.Xml;

namespace SxsNETCom
{
    [ComVisible(true)]
    [Guid("A7DD36C1-15A7-4849-9B35-36F597CE715E")]
    [ClassInterface(ClassInterfaceType.None)]
    public class ReadXml : IReadXml
    {
        public string ReadXmlFile(string filePath)
        {
            XmlDocument doc = new XmlDocument();
            doc.Load(filePath);

            return doc.OuterXml;
        }
    }
}

Again, we mark the class with ComVisible, assign unique Guid to it and define class interface type. In this simple case we use ClassInterfaceType.None. Next step to enable our Class library/DLL to be COM component is to sign it. This is simply done under project settings Signing tab.

Any key will do, if you don't have existing one you can simply generate new one by clicking on <New...> option.

At this point our DLL should be able to work as COM component. Next steps would be to generate TLB file for our component or to be precise Type Library Interface file which can be used by unmanaged clients to generate appropriate interface code for our component. We can use Regasm.exe or Tlbexp.exe to generate .tlb files. Since we want to avoid registration Tlbexp.exe will be better tool for the job. From Visual Studio Command Line we simple call following line:

C:\>tlbexp.exe [PathToDll]

This command will generate TLB file with same name as our COM component DLL name. TLB file will come in use later when we build unmanaged client application for our managed COM component.

Since we want to avoid global registration of our COM component, meaning writing into Windows registry, we need application manifest file.

To create an application manifest

Right click on project inside Visual Studio, go to Add New Item and choose "Application Manifest File". Visual studio will create app.manifest file inside your project with empty default settings. Next step is to embed our manifest into our dll. At the moment it is just part of our project but not actually used.

Embed application manifest into Class Library project

In case we had Console application project or Windows Form project we could simply go to Project settings and under Application tab , Icon and Manifest settings simply assign manifest. In case of Class library manifest option is grayed out so we need to add it manually to our .csproj file. Right click on project, click Unload project and then option for editing .csproj file will be available. Find this lines:

<PropertyGroup>
    <AssemblyOriginatorKeyFile>SignKey.snk</AssemblyOriginatorKeyFile>
  </PropertyGroup>
  <PropertyGroup />

And change it to:

<PropertyGroup>
    <AssemblyOriginatorKeyFile>SignKey.snk</AssemblyOriginatorKeyFile>
  </PropertyGroup>
  <PropertyGroup>
    <ApplicationManifest>app.manifest</ApplicationManifest>
  </PropertyGroup>

Visual Studio will complain about new "ApplicationManifest" element but it will work. Save the .csproj file and reload and rebuild project.

Now we need to open up and edit our app.manifest file to expose our COM components. This is how it should look like:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <assemblyIdentity 
    name="SxsNETCom" 
    version="1.0.0.0" 
    publicKeyToken="765d04d26ffa990b" 
    processorArchitecture="x86">
  </assemblyIdentity>
  <clrClass
    clsid="{A7DD36C1-15A7-4849-9B35-36F597CE715E}"
    progid="SxsNETCom.ReadXml"
    threadingModel="Both"
    name="SxsNETCom.ReadXml"
    runtimeVersion="v4.0.30319"></clrClass>
  <file name="SxsNETCom.dll" hashalg="SHA1"></file>
</assembly>

First important element is "assemblyIdentity", name atribute is name of the assembly we are producing, version is version defined in AssemblyInfo.cs. PublicKeyToken we get from yet another command line tool:

sn.exe -T [Path to DLL]

And here is the output produced by sn.exe

Next is processorArchitecture, if we build our Class library with platform target for x86 then we use it also here in manifest. In case we build for x64 then processorArchitecture should be "amd64" and in case we build with "Any CPU" then it should be defined as "msil". Important to mention here is that it is always better to build managed for specific architecture, namely x86 or x64, because our COM consumers are probably unmanaged and either x86 or x64.In case we build our managed COM component with "Any CPU" settings it means it will be at runtime ,depending on operating system architecture, compiled by Just-In-Time Compiler to either x86 or x64 assembly image which can break your 32 bit client running on 64 bit OS. To sum up, it is better to have 32 and 64 bit manifest for corresponding targets.

Next element in our manifest is clrClass. This is where we define types we want to expose as COM Component. Clsid represents GUID of our class, NOT interface. Name and progid are basically the same thing, fully qualified name of our class, namespace + class name. Threading model "Both" tells that our component can be used by STA and MTA, single and multithreaded clients. RuntimeVersion is version of .NET framework need for component to run. Ok so, now our managed COM component is ready, let's build unmanaged C++ native client.

Native Win32 COM Client

Right click on solution, add new project.

First thing we need to add ATL headers and TLB import statements to our stdafx.h.

#pragma once

#include "targetver.h"

#include <stdio.h>
#include <tchar.h>

#include <atlbase.h>
#include <atlcom.h>
#include <atlstr.h>
#include "atlsafe.h"

#import "..\tlb\SxsNETCom.tlb" no_namespace named_guids auto_rename

Now we are ready to use our COM component. Here is sample code:

// ComClient.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"


int _tmain(int argc, _TCHAR* argv[])
{
	CoInitialize(0);

	CComPtr<IReadXml> iReadXmlPtr;
	HRESULT hr = iReadXmlPtr.CoCreateInstance(CLSID_ReadXml);

	if (hr == S_OK)
	{
		_bstr_t filePath(L"C:\\temp\\simpleXml.xml");
		_bstr_t xml = iReadXmlPtr->ReadXmlFile(filePath);
		MessageBox(NULL, xml, L"Done", 0);
		iReadXmlPtr.Release();
	}
	else
	{
		MessageBox(NULL, L"Failed to initialize COM component", L"Failed", 0);
	}

	CoUninitialize();
	return 0;
}

Only thing we are still missing on client side is also a manifest file which will tell our application where to search for those COM types. We simple add following app.manifest to our C++ project.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <dependency>
    <dependentAssembly>
      <assemblyIdentity name="SxsNETCom"
                         version="1.0.0.0"
                         publicKeyToken="765d04d26ffa990b"
                         processorArchitecture="x86"/>
    </dependentAssembly>
  </dependency>
</assembly>

This time it is quite self explanatory. Dependent assembly simply describes our COM dll. Next and last step is to embed this manifest file into our executable. Once again, right click on project settings, Manifest Tool -> Input and Output. Under Additional Manifest Files we add name of our manifest file.

End result

Common Pitfalls

It is very important to know how Activation context is created and how manifests come into play. Even though we have embedded manifest into our COM client application, if we start debugging it from Visual Studio or better yet if we let Visual Studio initialize debugging by pressing F5 then our application will be loaded from inside VS hosting process and this way our embedded manifest is completely ignored and our application fails to initialize COM components. There is option to disable this behavior. To go around this, we need to first start our application and then attach visual studio to it.

Important to remember is that when creating Activation Context manifest from Caller, top process holding all other, will be used when searching for reg-free COM components. This means if client application A loads in way, by means of dynamic linking, reflection, COM, LoadLibrary() or in any other way, library B and library B tries to access reg-free COM components, ONLY application A has to have defined those COM components in it's manifest, since application A is in this case top process or caller, it creates Activation Context.

Application.EnableVisualStyles() and Reg-free COM Problem

In case we have WinForms application and it uses manifest as described above to create Activation Context and as we know WinForms comes out of the package with call to Application.EnableVisualStyles() in Main method, it will break creation of "our" activation context.  Application.EnableVisualStyles() in itself creates an activation context to redirect process to use Microsoft.Windows.Common-Controls version 6.0.0.0 and this breaks creation of Activation context we defined in our manifest.

One solution to this problem is to create our context at least one time before call to Application.EnableVisualStyles(). From managed code we can instantiate managed COM classes with something simple as this:

Type theType = Type.GetTypeFromCLSID(new Guid("A7DD36C1-15A7-4849-9B35-36F597CE715E"));
object instance = Activator.CreateInstance(theType);

Other and better solution is to comment out Application.EnableVisualStyles() and create activation context for Microsoft.Windows.Common-Controls ourselves by adding it to our manifest.

<dependency>
   <dependentAssembly>
     <assemblyIdentity
       type="win32"
       name="Microsoft.Windows.Common-Controls"
       version="6.0.0.0"
       processorArchitecture="X86"
       publicKeyToken="6595b64144ccf1df"
       language="*"
     />
   </dependentAssembly>
</dependency>

Including external manifest file

It is possible to reference external manifest from within our application manifest file. This way one manifest can be shared from multiple DLLs within same deployment directory. We need to define and reference it just as another dependency:

<dependency>
    <dependentAssembly>
      <assemblyIdentity name="ManifestFileNameWithouthExtension" version="1.0.0.0" type="win32" processorArchitecture="x86"></assemblyIdentity>
    </dependentAssembly>
  </dependency>

Source Code

SxsNETCom.zip (6.18 mb)

blog comments powered by Disqus

About me

Bizic Bojan is Co-Founder of Amida IT-Services GmbH and Software Architect with focus on .NET, C++, Python and Cloud Native solutions. 

 

Disclaimer:

The opinions expressed herein are my own personal opinions and do not represent my employer’s view in any way.

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.