Registration-Free Activation of .NET-Based Components: A Walkthrough

 

Steve White
Premier Support for Developers, Microsoft UK

Leslie Muller
Global IT Research & Development, Credit Suisse First Boston

July 2005

Summary: The Microsoft Platform SDK does an excellent job of documenting the topics of isolated applications and side-by-side assemblies. However, not everyone equates this topic with that of registration-free activation of COM components. Registration-free COM is a platform feature of great interest to enterprises with locked-down servers and applications isolated on shared infrastructures. This article walks through a working example of the registration-free activation of a .NET Framework-based component by native clients via COM interop. (11 printed pages)

Applies to:
   Microsoft Windows Server 2003
   Microsoft Windows XP Service Pack 2
   Microsoft .NET Framework version 1.1
   Microsoft Visual Studio .NET 2003
   Microsoft Visual Basic 6.0

Download the sample that accompanies this article, MSDNRegFreeNet.msi.

Contents

Introduction
Registration-Free COM Terminology
Running the Sample
Building a .NET Assembly As a COM Server
Building the Client
Registration-Free Activation
Troubleshooting
Conclusion
Further Reading

Introduction

Registration-free COM is a mechanism available on the Microsoft Windows XP (SP2 for .NET Framework-based components) and Microsoft Windows Server 2003 platforms. As the name suggests, the mechanism enables easy (e.g. XCOPY) deployment of COM components to a machine without the need to register them.

On the target platforms, one of the stages of initializing a process and its dependent modules is to load any associated manifest files into a memory structure called an activation context. In the absence of corresponding registry entries, it is an activation context that provides the binding and activation information the COM runtime needs. No special code is required in the COM server or in the client unless you choose to obviate the use of files by building activation contexts yourself using the activation context API.

In this walkthrough I will build a simple .NET assembly and consume it, both registered and unregistered, from native COM clients written in Visual C++ and Visual Basic 6.0. You can download the source code and samples and see them in action right away or you can follow along with the walkthrough and build them yourself step-by-step.

Registration-Free COM Terminology

Anyone familiar with .NET technology will be accustomed to the term assembly meaning a set of one or more modules deployed, named and versioned as a unit, with one module containing a manifest that defines the set. In registration-free COM, the terms assembly and manifest are borrowed for ideas that are similar in concept but not identical to their .NET counterparts.

Registration-free COM uses assembly to mean a set of one or more PE modules (i.e. either native or managed) deployed, named, and versioned as a unit. Registration-free COM uses manifest to refer to text files with the .manifest extension containing XML, which either defines the identity of an assembly (assembly manifest) together with the binding and activation details of its classes, or defines the identity of an application (application manifest) together with one or more assembly identity references. An assembly manifest file is named for the assembly, and an application manifest file is named for the application.

The term side-by-side (SxS) assemblies refers to the configuration of different versions of the same COM component, via manifest files, in order that they can be loaded simultaneously by different threads without needing to be registered. SxS enables, and is loosely synonymous with, registration-free COM.

Running the Sample

After you've downloaded and extracted the sample code, you'll find a folder called \deployed. In here is the Visual C++ version of the client application (client.exe), its manifest (client.exe.manifest), and the C# version of the COM server (SideBySide.dll). Go ahead and run client.exe. The expected result is that client.exe will activate an instance of SideBySideClass (implemented in SideBySide.dll) and display the result of calling its Version method which should look like "1.0.0-C#".

Building a .NET Assembly As a COM Server

Step 1

In Visual Studio .NET 2003 create a new C# or Visual Basic .NET Class Library Project and call it SideBySide. Remove the AssemblyInfo.[cs/vb] file from the project and implement a class as follows:

C# code

using System;
using System.Reflection;
using System.Runtime.InteropServices;

[assembly: AssemblyVersion("1.0.0.0")]
[assembly: Guid("[LIBID_SideBySide]")]

namespace SideBySide
{
   [Guid("[IID_ISideBySideClass]")]
   public interface ISideBySideClass
   {
      string Version();
   }

   [Guid("[CLSID_SideBySideClass]")]
   public class SideBySideClass : ISideBySideClass
   {
      public string Version()
      {
         return "1.0.0-C#";
      }
   }
}

Visual Basic .NET code

Imports System
Imports System.Reflection
Imports System.Runtime.InteropServices

<Assembly: AssemblyVersion("1.0.0.0")> 
<Assembly: Guid("[LIBID_SideBySide]")>

<Guid("[IID_ISideBySideClass]")> _
Public Interface ISideBySideClass
    Function Version() As String
End Interface

<Guid("[CLSID_SideBySideClass]")> _
Public Class SideBySideClass
    Implements ISideBySideClass
    Function Version() As String Implements ISideBySideClass.Version
        Version = "1.0.0-VB.NET"
    End Function
End Class

I've written the GUID values, which will be particular to your project, in the form of placeholders. You'll need to use the guidgen tool to generate unique GUIDs that will be the respective values I'll intend when I subsequently use the placeholders.

Step 2

So that a type library will be generated and registered at build time, set the project's Register for COM Interop setting to true.

Step 3

Produce a release build and copy SideBySide.dll into \deployed.

Building the Client

The next step is to build the client and for this walkthrough you have the option to build either a Visual C++ or a Visual Basic 6.0 client.

Step 4 (Option A: Visual C++)

Create a new Visual C++ Win32 Console Project called client in a sibling folder relative to the SideBySide project's folder. In the Win32 Application Wizard, on the Application Settings tab, check the Add support for ATL checkbox.

Edit stdafx.h and add the following line at the top of the file, immediately after the #pragma once:

#define _WIN32_DCOM

Also in stdafx.h add the following line at the bottom of the file:

import "[path]\SideBySide.tlb" no_namespace

Here, [path] should be the relative path to the type library that was generated when you built your SideBySide assembly. This path usually varies depending on whether you chose a C# or a Visual Basic .NET project in Step 1.

Replace the contents of client.cpp with this code:

#include "stdafx.h"
#include <iostream>
using namespace std;

void ErrorDescription(HRESULT hr)
{
    TCHAR* szErrMsg;
    if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|
      FORMAT_MESSAGE_FROM_SYSTEM, 
      NULL, hr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 
      (LPTSTR)&szErrMsg, 0, NULL) != 0)
   {
        cout << szErrMsg << endl;
        LocalFree(szErrMsg);
    }
   else
        cout << "Could not find a description for error 0x" 
           << hex << hr << dec << endl;
}

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

   {
      ISideBySideClassPtr ptr;
      HRESULT hr = 
              ptr.CreateInstance(__uuidof(SideBySideClass));
      if (SUCCEEDED(hr))
      {
         cout << ptr->Version() << endl;
      }
      ErrorDescription(hr);

      char c;
      cin >> c;
   }

   CoUninitialize();

   return 0;
}

Produce a release build and copy \release\client.exe into \deployed.

Step 4 (Option B: Visual Basic 6.0)

Create a new Visual Basic 6.0 Standard EXE project. In the Project Explorer select the Project1 node and, in the Properties Window, change its name to client. Choose File | Save Project As and save the form file and the project file in a sibling folder relative to the SideBySide project's folder. Choose Project | References, check the checkbox next to SideBySide and choose OK.

Double-click the main form in the form designer and paste the following code inside Sub Form_Load():

    Dim obj As New SideBySideClass
    Dim isxs As SideBySide.ISideBySideClass
    Set isxs = obj
    MsgBox isxs.Version()

Choose File | Make client.exe... and navigate to the \deployed folder and then choose OK.

Step 5

At present the \deployed folder should contain, apart from some intermediate files, only client.exe and SideBySide.dll; and the latter will have been registered by its build process. To check that your server and client work together under these normal circumstances, run \deployed\client.exe and note the expected output "1.0.0-C#" or "1.0.0-VB.NET".

Step 6

This walkthrough is about registration-free COM, so we now need to unregister the SideBySide assembly. At a Visual Studio 2003 Command Prompt, navigate to the \deployed folder and execute the command: regasm /u SideBySide.dll.

Step 7

To see what effect the previous step has had, run \deployed\client.exe again and you will see the message "Class not registered" or "Run-time error '429': ActiveX component can't create object". At this stage we have frustrated the COM runtime from finding the information it needs in the registry but we have yet to make the information available by alternative means. We will remedy that in the following steps.

Registration-Free Activation

Step 8

In the \deployed folder, create an application manifest file (a text file) for the client.exe application and call it client.exe.manifest. Paste the following into the file:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" 
   manifestVersion="1.0">
<assemblyIdentity
            type = "win32"
            name = "client"
            version = "1.0.0.0" />
<dependency>
            <dependentAssembly>
                        <assemblyIdentity
                                    type="win32"
                                    name="SideBySide"
                                    version="1.0.0.0" />
            </dependentAssembly>
</dependency>
</assembly>

Step 9

In the SideBySide project's folder, create a private assembly manifest file (a text file) and call it SideBySide.manifest. Paste the following into the file:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" 
  manifestVersion="1.0">
<assemblyIdentity
            type="win32"
            name=" SideBySide"
            version="1.0.0.0" />
<clrClass
            clsid="{[CLSID_SideBySideClass]}"
            progid="SideBySide.SideBySide"
            threadingModel="Both"
            name="SideBySide.SideBySideClass" >
</clrClass>
</assembly>

The next task is to embed the above assembly manifest file into the SideBySide assembly as a Win32 resource. At the time of writing, this is mandatory for Windows XP, but not for Windows Server 2003. On Windows Server 2003 you can get away with simply deploying the assembly manifest file alongside the assembly. However, I urge you not to depend on this behavior as it may well change in an upcoming Windows Server 2003 Service Pack. To ensure you continue to support both platforms in the future, follow the next few steps and embed your assembly manifest file into your .NET assembly as a Win32 resource. This is only necessary for registration-free activation of .NET-based components and is not a requirement for registration-free activation of native COM components.

Step 10

In the SideBySide project's folder create a resource-definition script file (a text file) and call it SideBySide.rc. Paste the following into the file:

#include <windows.h>
#define MANIFEST_RESOURCE_ID 1
MANIFEST_RESOURCE_ID RT_MANIFEST SideBySide.manifest

The windows.h file and its dependencies are available when you install either the Platform SDK (Core SDK section) or Visual C++. The part of windows.h that is required here is the definition:

#define RT_MANIFEST 24

Consequently, the contents of SideBySide.rc resolve to:

1 24 SideBySide.manifest

However, it's clearer and more general to use the macro definitions as directed.

Step 11

In the SideBySide project's folder create a build command file (a text file) and call it build.cmd. Paste the following into the file:

To build C#:

rc SideBySide.rc
csc /t:library /out:..\deployed\SideBySide.dll 
/win32res:SideBySide.res Class1.cs

To build Visual Basic .NET:

rc SideBySide.rc
vbc /t:library /out:..\deployed\SideBySide.dll 
/win32resource:SideBySide.res /rootnamespace:SideBySide Class1.vb

What these commands do is first to invoke the Microsoft Windows Resource Compiler tool from the Platform SDK (rc.exe) to compile the resource-definition script from step 10 into a compiled resource file called SideBySide.res. Next, it invokes the C# or Visual Basic .NET compiler to build your source code file into an assembly and embed the compiled resource file into it as a Win32 resource. The compiled assembly is written to the \deployed folder but it is not registered for COM interop.

Step 12

At a Visual Studio 2003 Command Prompt, navigate to the SideBySide project's folder and execute the command: build.

Step 13

To verify that, courtesy of the manifest files, your client is once more able to activate the SideBySideClass class via COM interop, run \deployed\client.exe and note the expected output "1.0.0-C#" or "1.0.0-VB.NET".

Troubleshooting

As we've seen, the registration-free activation of .NET Framework-based components requires no special code in the server or in the client. All that's required is a matching pair of manifest files, one of which is embedded into the .NET assembly as a Win32 resource of type RT_MANIFEST.

I suggest you approach your own registration-free development the way this walkthrough does. Specifically: first get to a known state by seeing your client working with a registered server; then unregister the server and confirm that your error message is what you'd expected; and finally remedy the situation by crafting and deploying manifest files. This way your troubleshooting efforts around registration-free activation will be confined to the structure of your manifest files and the correct embedding of the assembly manifest.

When troubleshooting registration-free COM issues, the Event Viewer on Windows Server 2003 is your friend. When Windows XP or Windows Server 2003 detects a configuration error it will typically show an error message box titled for the application you have launched and containing the message "This application has failed to start because the application configuration is incorrect. Reinstalling the application may fix this problem." I advise that whenever you see this message you reproduce the problem on Windows Server 2003, consult the System Event Log and look for events from the SideBySide source. The reason I don't suggest that you look at XP's Event Log in these cases is that it will invariably contain a message such as "Generate Activation Context failed for [path]\[application filename].Manifest. Reference error message: The operation completed successfully," which doesn't help identify the problem.

Before moving on to the structure of manifest files, let's talk about Win32 resources. As I mentioned above, windows.h defines the RT_MANIFEST symbol as the value 24, which is the value the operating system will recognize as an embedded manifest file. If you forget to include windows.h in your resource-definition script (.rc file) then your build will still succeed and your manifest file will still be embedded as a resource, but not of the correct type. To verify that you have correctly embedded its manifest, open your SideBySide.dll in Visual Studio (File | Open | File...). You'll see a tree view showing the resources inside the module. Under the root node should be a node named RT_MANIFEST underneath which should be another node showing the resource number of your manifest resource (1 in the walkthrough). Double-click this last node to see the data in a binary view and give it a quick sanity check that it resembles your XML manifest file. Although it is binary, the characters from the ASCII range will be obvious. If the binary data is missing or looks incorrect then verify that your resource-definition script (.rc file) references your manifest file. If the text of the RT_MANIFEST node is in quotation marks then you have likely forgotten to include windows.h in your resource-definition script (.rc file).

Each of the errors just mentioned will be reported in the Windows Server 2003 System Event Log with the message: "Dependent Assembly [name] could not be found and Last Error was The referenced assembly is not installed on your system."

The schemas of the various manifest files are documented in the Platform SDK under the heading Manifest Files Reference, and the schema validation tool Manifestchk.vbs is available, so here I will only call out a few points relevant to the walkthrough. First let's examine the assembly manifest file. For an example, look back to Step 9.

You will recall that, in the registration-free COM sense, an assembly is an abstract idea with which you associate one or more physical files by means of the contents of the assembly manifest file.

The assemblyIdentity element defines the identity of the assembly. For .NET-based components its name attribute must match the .NET assembly's name and hence its filename, otherwise you will see the following message in the Windows Server 2003 System Event Log: "Dependent Assembly [value of name attribute] could not be found and Last Error was The referenced assembly is not installed on your system." However, the version attribute needn't match the .NET assembly's AssemblyVersion, nor its AssemblyFileVersion, although it's a good idea to apply some kind of consistency.

The clrClass element has only two mandatory attributes: name and clsid. The name attribute must match the combined namespace and class name of the CLR class being activated. If it does not, then CoCreateInstance will return a HRESULT with the value COR_E_TYPELOAD (0x80131522). This results from a System.TypeLoadException being thrown when the type loader fails to find the requested CLR type in your .NET assembly. One thing to watch for if your .NET assembly is written in Visual Basic .NET is that you are supplying the /rootnamespace switch on the command line to the Visual Basic .NET compiler (vbc.exe). The clsid attribute must match the CLSID assigned to the CLR class being activated via its GuidAttribute. If it does not, then CoCreateInstance will return a HRESULT with the value REGDB_E_CLASSNOTREG (0x80040154), the message text for which is "Class not registered".

Now let's turn our attention to the application manifest file. For an example, look back to Step 8. The application manifest must be named in the format [application filename].manifest. So, in the walkthrough it was named client.exe.manifest to make it clear it should be read whenever client.exe is loaded into a process. If this is not done correctly then CoCreateInstance will return a HRESULT with the value REGDB_E_CLASSNOTREG (0x80040154), the message text for which is "Class not registered".

The most important element in the application manifest is the dependentAssembly/assemblyIdentity element. This element is a reference to the equivalent one in the assembly manifest and the two have to match exactly. A good way to ensure that they do so is to copy the element from the assembly manifest and paste it here. If there is any difference whatsoever, you will see the following message in the Windows Server 2003 System Event Log: "Component identity found in manifest does not match the identity of the component requested."

Conclusion

Registration-free COM is a technology that liberates COM components from a dependency on the Windows registry and consequently liberates the applications that use them from requiring dedicated servers. It enables applications with dependencies on different versions of the same COM component to share an infrastructure and to load those various COM component versions side by side in an echo of the .NET versioning and deployment mechanism.

This article walked you through a demonstration of the registration-free activation of .NET Framework-based components by native client applications written in both Visual C++ and Visual Basic 6.0. It explained some of how the mechanism works and underlined some possible configuration errors and how to troubleshoot them.

Further Reading

 

About the author

Steve White is an Application Development Consultant working in the Premier Support for Developers team at Microsoft UK. He supports customers developing with Visual C#, Windows Forms and ASP.NET. His blog has more information about his interests in music, visualizations, and programming.

Leslie Muller is a technologist with the Research & Development team at Credit Suisse First Boston. Leslie has 12 years experience as a developer and technical architect, working in environments such as financial services, technology startups, industrial automation and defence. When he’s not programming or doing research he enjoys skiing, ice hockey and when possible doing slightly mad things with motorized vehicles in extreme environments like Iceland or the Rockies.