Monday, August 20, 2018

Calling .NET From COM
Originally posted by Mikec276 on C Sharp Friends

It might be hard to convince your IT manager to let you build your entire system from scratch in C#, just because you think it is cool. A better trick is to start building new bits with .NET, and slowly integrate them into the old system before anyone has a chance to discover what you are up to ;-). But for that you have to be able to call them from your existing COM systems.

It turns out that this is very easy. Here is a simple example of calling a C# object from a COM technology platform (vbscript).

General Steps

1. Build a simple C# class, inside say a Library assembly (DLL).

2. Use the Framework-provided REGASM tool to add the registry entries necessary for it to be called via COM.

3. Call your object from the 'legacy' code.

The C# Class

A simple class to demonstrate this functionality, with a couple of simple string and int properties, is as follows:
using System;

namespace SampledotNETDLL
{
    public class Customer
    {
        private string msName;
        private int miHatSize;

        public Customer()
        //default constructor needed to access from COM.
        {
            this.CustomerName = "John Doe";
            miHatSize = 12;

        }

        public void Init(string Name)
        {
            this.CustomerName = Name;
            Random rand = new Random();
            miHatSize = rand.Next(8)+7;
        }

        public string CustomerName
        {
            get    { return msName; }
            set     { msName = value; }
        }

        public int HatSize
        {
            get    { return miHatSize; }
        }
    }
}
Note the use of a public no-args constructor. For this to be called from COM it has to have a public no-args constructor. Any member-specific initialisation is done in a separate method 'Init'. Build this class, then we will register it so that COM can call it.

Registering the .NET Assembly

The .net framework provides a tool called REGASM.EXE, that you can use to create the necessary registry entries for your new assembly. It is simple to use, from the command prompt simply type:
REGASM [path to assembly]
The assembly will then be registered in the HKEY_LOCAL_MACHINE hive of the registry, just like a COM DLL. Note if you see this error:

RegAsm warning: No types were registered

Then it may be because your potential COM-accessible classes do not have no-args constructors required to make them COM-able (yes I discovered this the hard way ;-) ).

Call the Object with COM

For this example we will call the Customer object(s) from Vbscript. For this to work, we need to copy the DLL into the path of the wscript/cscript applications, which are found in C:\WINNT\System32. I will explain later how we can avoid this step. Then create a VBScript file, something like the following:
dim oCustomer
set oCustomer = CreateObject("SampledotNETDLL.Customer")
if err.number <> 0 then
    msgbox "Error " & Err.number & " Creating Customer: " 
   & Err.description
else
    msgbox "Customer exists: " & Cstr(not(oCustomer is nothing))
    oCustomer.Init "Frank Smith"
    msgbox "Customer Name: " & oCustomer.CustomerName &
             " Hat Size: " & oCustomer.Hatsize
end if
Note that the CreateObject uses [namespace].[classname] as the ProgID. We can change this, more on this later. If we run this you should get two message boxes, one saying

Customer exists: True

The other saying

Customer Name: Frank Smith Hat Size: [some number]

You can of course alter the code to suit your requirements, calling the C# code from VB, C++, Jscript or any other COM-enabled 'client'.

Changing the ProgID

You can give your class any ProgID you like, if you don't like the default of [namespace].[classname]. To do this, simply add
using System.Runtime.InteropServices;
to your class, and give the class itself the following attribute:
[ProgID("MySample.Customer")]
...which would change the ProgID to "MySample.Customer", for example. You can of course change this to whatever you desire. If you add this in remember to use REGASM on your new DLL once it is rebuilt ;-).

Making the Assembly Globally Accessible

In the above example we had to copy the assembly into the path of the 'client', which was the scripting host (wscript or cscript). To avoid this step, and make the assembly globally accessible, we simply give the assembly a strong name, and register it with the .net Global Assembly Cache (GAC) in the usual manner. I'm going to assume you know how to do that, as it is a general .NET detail and not specific to COM interoperability.

Using the GAC works because the .NET CLR is, at runtime, creating what is know as a COM Callable Wrapper (CCW) for your assembly. If your assembly is not in the GAC, and not in the path of the client, the CCW simply won't find your assembly, just as for standard multi-assembly .NET programming. Incidently the error message you get back is not overly helpful (yes I found this out the hard way too ;-) ), the VB Error object has the following data in it:

Error Number 0x80131522 (-2146233054)

Source: (null)

If you ever see this, then what it means is that the CCW can't find your assembly for some reason - it is not in the path of the client, nor was it found in the GAC. or any other COM-enabled 'client'.

Take it to the Streets

So now you have the knowledge you need to infiltrate your existing legacy systems with shiny new C# objects (which is what we REALLY want to write things in, right? ;-) ). 

No comments:

Post a Comment

Calling .NET From COM Originally posted by Mikec276 on C Sharp Friends It might be hard to convince your IT manager to let you build y...