Creating a COM object from scratch with C#

What is a COM object

COM (Component Object Model) is a very outdated software standard that has since been largely superceded by the .NET framework. The main purpose for its existence is that regardless of the language it’s programmed in, its members, when exposed via unique GUIDs, can be accessed by different processes written in different languages, thus giving rise to interprocess communication. Although I encourage you to create .NET objects, there are situations where creating a COM object is the better, if not the only option available to you. A good example of this is writing external routines for SQL databases that operate on SQL 2000 or below, or SQL 2005/2008 databases that operate in 2000 compatibility mode. SQL 2000 or below only supports calling COM objects from its stored procedures where as SQL 2005 or above introduces the ability to enable CLR (Common Language Runtime) on a database, which enables programmers to bind external .NET objects to SQL functions and stored procedures.

Creating a COM object in C#

There are various bits of information available on the Internet that show you how to do this. However, none of them seems to take you from the beginning to the end. This is what I will strive to achieve in this blog post.

Prerequisites:

  1. Microsoft Visual C# 2010 Express (download here)
  2. The guidgen.exe tool (download here)
  3. The sn.exe tool (if you have Visual studio installed, then it’s found here: C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin and if not, you can download the .NET SDK here)
  4. Have at least .NET Framework 2.0 installed (latest version can be downloaded here)
  5. The regasm.exe tool, found here after 4. is satisfied: (for .NET 2.0, 3.0 or 3.5) %windir%\Microsoft.NET\Framework\v2.0.50727; (for .NET 4.0) %windir%\Microsoft.NET\Framework\v4.0.30319
  6. Some experience with .NET programming (not essential, but definitely helps with understanding the example here. Note that this can be translated into VB .NET, you just need to know the corresponding syntaxes.)

Steps:

1. Creating a new project in C#

Start by creating a new project in C# Express, when presented with the templates to choose from, select Class Library, give it a meaningful name (I shall name it ComClassExample) and hit OK. This will create two .cs files, one being AssemblyInfo.cs, which is common to all class libraries and class1.cs, which you can rename to something more meaningful. Renaming it will also rename your public class name (I will rename it to ComClassExample.cs). Save the project (File -> Save all). Open the ComClassExample.cs file so we can start editing.

Before we start creating our class, add this namespace to your existing list; our entire example will make use of it:

using System.Runtime.InteropServices;

2. Constructing the class

The first thing to consider is of course the class itself. Create a basic class with a constructor and simple method that returns a value of int type:

public class ComClassExample
{
 // constructor - does nothing in this example
 public ComClassExample() {}

 // a method that returns an int
 public int AddTheseUp(int adder1, int adder2)
 {
  return adder1 + adder2;
 }
}

3. Presenting our class through an interface

Remember that even though the eventual objective of this exercise is to expose the dll to the world of COM, the code you are writing is still managed and there needs to be a way to tell the COM clients how to access the public methods/properties of your class. You will also need to determine how you want your COM objects to be called – at compile-time or run-time (i.e. early binding or late binding). Script languages such as Javascript are not compiled and thus have no way of determining the type of COM object it’s calling (in other words, they only support late-binding). Therefore, it’s a lot safer to enable an interface for late-binding than early binding (you can also enable both) unless you have a really good reason to exclude late-binding.

Create a public interface and add an abstract definition of every method or property you wish to expose from your class object in the interface. You can optionally add the attribute [DispId] to each method to manually control how COM services will call them.

public interface IComClassExample
{
 [DispId(1)]
 int AddTheseUp(int adder1, int adder2);
}

4. Making the connection

OK, so we have the flesh and blood of the code covered, we will need bones and nerves to correlate them. Firstly, we need to let our class know which interface will be responsible for exposing its methods (you can have multiple interfaces and classes in your namespace if you want, but we will stick with 1 in this example for simplicity’s sake). You can achieve this by derivation, which in this case is to implement the interface. Since we manually created the interface, we need to tell the compiler not to generate another automatically as this will cause confusion. We can do this by using the ClassInterface attribute and setting its value to ClassInterfaceType.None. The code for your class will now look like this:

[ClassInterface(ClassInterfaceType.None)]
public class ComClassExample : IComClassExample
{
  // constructor - does nothing in this example
  public ComClassExample() { }

  // a method that returns an int
  public int AddTheseUp(int adder1, int adder2)
  {
     return adder1 + adder2;
  }
}

Back to our interface, by default, an interface is enabled for both early and late-binding, however in this case we want it to be set to late-binding only. We can use the InterafaceType attribute to achieve this. Note that the InterfaceIsDispatch type corresponds to late-binding (see here for more info about IDispatch and IUnknown interfaces):

[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IComClassExample {
 [DispId(1)]
 int AddTheseUp(int adder1, int adder2);
}

5. Making them unique

Every COM assembly and its public types (i.e. interfaces, classes and et cetera) need a unique GUID. By default, Visual C# 2010 assigns a unique GUID to the entire assembly automatically at compile-time, but it does not do so for the public types. You have two options here. You can either manually generate GUIDs with the guidgen.exe tool and assign them to each type individually or you can use regasm.exe to automatically generate them for you. In this case, we are going to proceed with the former approach.

Start up guidgen.exe and pick Registry format. Generate a new GUID for each of your public types (in the case of this example, one class plus one interface) and paste it into the Guid attribute. Your code should now resemble something like this, note that I moved the square brackets to accommendate more attributes and a comma is used to separate them:

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

namespace ComClassExample
{
 [Guid("7068AC34-DBB0-4e40-84A7-C2243355E2D7"),
 InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
 public interface IComClassExample
 {
  [DispId(1)]
  int AddTheseUp(int adder1, int adder2);
 }

 [Guid("863AEADA-EE73-4f4a-ABC0-3FB384CB41AA"),
 ClassInterface(ClassInterfaceType.None)]
 public class ComClassExample : IComClassExample
 {
  // constructor - does nothing in this example
  public ComClassExample() { }

  // a method that returns an int
  public int AddTheseUp(int adder1, int adder2)
  {
    return adder1 + adder2;
  }
 }
}

6. Going underground with cryptography

We are almost done. We will now need to give our assembly a strong name. A strong name gives this assembly a unique signature and allows it and its cousins (i.e. different versions of the same module) to run side-by-side. We do this with the sn.exe utility.

Run the following command and copy the resulting file to the project’s root directory (c:\Users\John.Doe\My Documents\Visual Studio 2010\Projects\ComClassExample\ComClassExample):

sn -k ComClassExample.snk

Now we need to add a link to the .snk file to your project. Right click on your project name -> Add -> Existing item. Browse to your project directory, highlight the .snk file (You might have to change file types drop down to All Files in order for the file to become visible), click on the down arrow beside the Add button and  select Add as Link. With that done, we now need to make the compiler aware that we want to use this .snk file to sign our assembly with a strong name.  Go to the project properties, select the Signing tab and click on Sign the assembly to enable the section. Select .snk file from the dropdown list and save the project.

7. Wrapping it all up

There are a few more project options that we need to modify before we F6 the solution. Go to project properties once again. This time, go to the Application tab. Under Target framework, choose your desired version. Your decision on this will be influenced by factors such as which .NET Framework version the target machines that you will using this dll on have installed. We will select .NET Framework 4 for this example. Now click on the Assembly Information… button. Ensure that the Make assembly COM-Visible option is checked. Close the dialogue box. Now click on the Build tab and check the Rigister for COM interop box. Save your project

Just a note on the Make assembly COM-Visible option. If you open up the AssemblyInfo.cs file, you will notice that the [assembly: ComVisible()] option has now been set to true. If you have a large assembly that contains multiple classes and interfaces, you might not wish to expose every one of them to COM. In that case, you can set this option to false (or do so through project properties as mentioned above), and explicitly tag every interface/class that you wish to expose with the ComVisible attribute. This obviously gives you granular control over their COM visibility; however, for the purpose of this example, we will want to expose everything and making the assembly globally COM-visible seems more appropriate.

8. Compiling it and taking it out for a walk

We can now compile our assembly by clicking on Build -> Build Solution. It should compile without any warnings or errors. The compiler does all the registration for you on your development PC, so you don’t have to manually do so.

There are all sorts of ways to test this. Here are three ways:

i) Vbscript

Create a new .vbs file, edit it with the following lines, save it and run it:

dim objTest, intResult
Set objTest = WScript.CreateObject ("ComClassExample.ComClassExample")

intResult = objTest.AddTheseUp (100, 200)
Wscript.echo "Result = " & intResult

ii) Powershell

Start up Powershell ISE, edit it with the following code and hit F5 to run:

$object = new-object -ComObject ComClassExample.ComClassExample
write-host $object.AddTheseUp(100, 200)

iii) Javascript

Create a new .html file, insert the following code and run it through a browser (you might want to enable ActiveX):

<HTML>
<HEAD>
<SCRIPT language="javascript">
function Test() {
var objTest = new ActiveXObject("ComClassExample.ComClassExample");
alert(objTest.AddTheseUp(100, 200));
}
</SCRIPT>
</HEAD>
<BODY>
<INPUT Type="button" onClick="Test()" Value="Click here to test!">
</BODY>
</HTML>

9. Deploying the dll on another machine

It’s all rosy on your development machine. What if you want to deploy the dll to another machine? It’s very simple.

Copy the dll (should be located in your project directory/bin/release) to your target machine. Ignore the .pdb and .tlb files. Go to the target machine and locate the regasm.exe tool (again depending on the version of .NET framework your dll is compiled in, choose the most appropriate version of regasm.exe. If in doubt, refer to the prerequisites section of this blog). Run the following command to register the dll.

regasm ComClassExample.dll /codebase /tlb /nologo

That’s pretty much it. You can use the script examples in 8. to test the dll out on the target machine. I hope I have been able to help you. If you have any questions, please leave a comment below. Thanks.

Advertisements

2 responses to “Creating a COM object from scratch with C#

  1. I found the solution: I inverted lines with guid

    like
    [Guid(“863AEADA-EE73-4f4a-ABC0-3FB384CB41AA”),
    ClassInterface(ClassInterfaceType.None)]

    changed in

    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch), Guid(“7068AC34-DBB0-4e40-84A7-C2243355E2D7”)]

    Now work

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s