Building a COM interop library for ASP Classic using 4.0 framework and Visual Studio 2010

After going through a number of different articles and not finding anything especially conclusive that takes me step-by-step through the process, I've come seeking help.

The Scenario

A client of mine is only proficient in development for ASP Classic. They have recently acquired an account for a site originally written in ASP.NET. They are rolling the site into something they can actively maintain, but the site originally included an image handler that took dynamically changing data regarding water levels and outputs an image containing a graphical representation of that data. The requirement is to develop a COM interop library that can be registered on the server and called with CreateObject to generate the same image's byte array for output using Response.BinaryWrite. The COM interop library must be registered at the remote site on a Windows 2000 Server, and I can't make any assumptions about their having access to regasm/gacutil to accomplish that task.

The Difficulty

I've built the class library by creating a Class Library project in Visual Studio 2010, choosing "COM Class" from the template, and inserting my code to generate a class with a single public method to return a byte array when given an integer (well, enumerator, but all the same). Unfortunately, even on my own development machine after building the library and registering (regasm) and caching the assembly (gacutil), I can't make a call through Classic ASP to instantiate the object, receiving instead an "ActiveX component can't create object" error. And, of course, at the server site, the DLL file can't be registered, the response being "Required module was not found."

Resources I've Used

I've already had a look through the following articles and haven't turned up the answers I need:

  1. (Basic steps) Walkthrough: Creating COM Objects with Visual Basic
  2. Build and Deploy a .NET COM Assembly
  3. .NET COM+ Interop Component with Classic ASP

What I Need

Essentially what I need is a bit of hand-holding on a kind of step by step of what it's going to take to meet the requirements and create a COM+ interop module correctly in Visual Studio 2010. Creating the actual class object itself isn't terribly difficult.

However, none of the articles I've looked through really discuss project options or build procedures with Visual Studio 2010 or the .NET 4.0 Framework, nor have any of them really discussed if there are special considerations for deploying to older systems like Windows Server 2000 and the actual registration of the library on a system with only, say, regsvr32 on hand.


It should be fairly straightforward to get a basic .NET assembly exposed to COM - I've never tried the COM Class project template, so this is the way I've managed it in the past:

Create a new (bog standard) .NET class library using C# or VB. Define a COM interface (replace GUIDs with your own):

[ComVisible(true)]
[Guid("8999F93E-52F6-4E29-BA64-0ADC22A1FB11")]
public interface IComm
{
    string GetMyGroups();
}

Now define a class that implements that interface (again, replace GUIDs with your own):

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[GuidAttribute("C5C5A1A8-9BFB-4CE5-B42C-4E6688F6840B")]
[ProgId("Test.Comm.1")]
public class Comm : IComm
{
    public string GetMyGroups()
    {
        var comm = new CommunicatorAPI.MessengerClass();

        var groups = comm.MyGroups as IMessengerGroups;
        return string.Join(", ", groups.OfType<IMessengerGroup>().Select(g => g.Name).ToArray());
    }
}

The Prog ID attribute on this class is what you will use to instantiate your component from ASP.

Strongly-name the assembly (Project properties -> "Signing" tab -> "Sign the assembly" -> Create a new strong name key file using the dropdown)

Now, build the assembly, and register using Regasm - if you don't wish to register in the GAC (which i'd recommend, as not GACing keeps the deployment simpler), be sure to use the -Codebase parameter (this just adds a reg entry that tells clients where to find the assembly) - e.g:

regasm ClassLibrary2.dll /codebase "S:\Testing\ClassLibrary2\ClassLibrary2\bin\Debug\ClassLibrary2.dll"

Now you should be able to instantiate the component, and call methods on it - for example (in javascript):

var a = new ActiveXObject("Test.Comm.1");
alert(a.GetMyGroups());

When it comes to deployment, the important work that Regasm and Regsvr32 do is to write various settings into the registry, so that clients can find the COM component (based on Prog ID, or COM Class ID). All you need to do is work out what COM settings are being written when you run Regasm on your local machine, and write these to the registry on the server. You can use ProcMon to monitor what gets written to the registry when Regasm is run.

Generally speaking, you can expect to see something like this written to the registry:

[HKEY_CLASSES_ROOT\Test.Comm.1]
@="ClassLibrary2.Comm"

[HKEY_CLASSES_ROOT\Test.Comm.1\CLSID]
@="{00585504-90C8-4760-A359-67CAF08FFED1}"

[HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{00585504-90C8-4760-A359-67CAF08FFED1}]
@="ClassLibrary2.Comm"

[HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{00585504-90C8-4760-A359-67CAF08FFED1}\Implemented Categories]

[HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{00585504-90C8-4760-A359-67CAF08FFED1}\Implemented Categories\{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}]

[HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{00585504-90C8-4760-A359-67CAF08FFED1}\InprocServer32]
@="mscoree.dll"
"ThreadingModel"="Both"
"Class"="ClassLibrary2.Comm"
"Assembly"="ClassLibrary2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cf55d4e60653257a"
"RuntimeVersion"="v4.0.30319"
"CodeBase"="file:///S:/Testing/ClassLibrary2/ClassLibrary2/bin/Debug/ClassLibrary2.DLL"

[HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{00585504-90C8-4760-A359-67CAF08FFED1}\InprocServer32\1.0.0.0]
"Class"="ClassLibrary2.Comm"
"Assembly"="ClassLibrary2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cf55d4e60653257a"
"RuntimeVersion"="v4.0.30319"
"CodeBase"="file:///S:/Testing/ClassLibrary2/ClassLibrary2/bin/Debug/ClassLibrary2.DLL"

[HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{00585504-90C8-4760-A359-67CAF08FFED1}\ProgId]
@="Test.Comm.1"

Hope this helps :)


I have a Classic ASP web site that uses a VB6 COM object. I wanted to create a new version of the COM object using .NET instead of VB6. This is how I did it (hope this helps). I include instructions for both C# and VB.NET.

[01]

  • Start Visual Studio 2015 (run as admin).
  • Create a new "Class Library" project.
  • Name it: "DotNetCom"

[02] C#

  • Add a new class, name it "HelloCOM".
  • Use the following code as starting template

( visit https://msdn.microsoft.com/en-us/library/c3fd4a20.aspx for more info )

    using System.Runtime.InteropServices;

    namespace DotNetCom
    {
        [Guid("EAA4976A-45C3-4BC5-BC0B-E474F4C3C83F")]
        public interface HelloCOMInterface
        {
            [DispId(1)]
            string Hello();
        }

        [Guid("7BD20046-DF8C-44A6-8F6B-687FAA26FA71"), 
            InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
        public interface HelloCOMEvents 
        {
        }

        [Guid("0D53A3E8-E51A-49C7-944E-E72A2064F938"),
            ClassInterface(ClassInterfaceType.None),
            ComSourceInterfaces(typeof(HelloCOMEvents))]
        public class HelloCOM : HelloCOMInterface
        {
            public string Hello()
            {
                return "Hello there!";
            }
        }
    }

[02] VB.NET

  • Add a new "COM class", name it "HelloCOM".
  • VB.NET creates the starting template.
  • Add the following function to the "HelloCOM" class.

    Public Function Hello() As String
        Return "Hello there!"
    End Function
    

[03] C#

  • Open the project properties.
  • Go to "Application".
  • Click "Assembly Information...".
  • Check "Make assembly COM-Visible"
  • Go to "Build".
  • Select "Platform target: x86".
  • Check "Register COM for interop"

[03] VB.NET

  • Open "MyProject".
  • Go to "Compile".
  • Select "Target CPU: x86".

[04]

  • Build the "DotNetCom.dll".

[05]

  • Open a command prompt (run as admin).
  • Change directory to your dll.

    cd DotNetComTest\DotNetComTest\TX7NGN.COM\bin\Debug
    
  • Run RegAsm /codebase.

    C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm DotNetComTest.dll /codebase "C:\DotNetComTest\DotNetComTest\bin\Debug\DotNetComTest.dll"
    

[06]

  • Start Component Services.
  • Add a new COM+ application.
  • Name it: "DotNetCom".
  • Open the "DotNetCom" properties.
  • Go to the "Security Tab".
  • UNCHECK "Enforce access checks for this application".

[07]

  • Add a new component.
  • Select "DotNetComTest.tlb" (do NOT select "DotNetComTest.dll").

[08]

  • Use the COM object from the Classic ASP page.

    <%
    Dim HelloCOM
    Set HelloCOM = Server.CreateObject("DotNetCom.HelloCOM")
    Response.Write HelloCom.Hello
    %>