Creating a .NET .dll that can be used by unmanaged code

There is a fundamental difference between the world of managed .NET and the world of unmanaged COM. In the .NET world, everything derives from the base class Object, everything but value types are in actuality pointers to objects created on the heap, and the omnipresent garbage collector automatically cleans up dangling pointers when the objects they refer to go out of scope. All communications between .NET objects occur through Objects. In contrast, COM objects expose their functionality with a set of interfaces - a sort of binary contract called Type libraries.

.NET COM
Object based communication Interface based communication
Garbage Collector to manage memory Reference count will be used to manage memory
Type Standard objects Binary Standard objects
Objects are created by new operatorObjects are created by coCreateInstance
Exceptions will be returned HRESULT will be returned
Object info resides in assembly files Object info resides in Type library

A COM object cannot understand the .NET way of communicating through Objects, so a .NET object must be "wrapped" before it can be used by an unmanaged COM object.

Developing the .NET class

At the top of your class, include the statement using System.Runtime.InteropServices;

Immediately before the class you want to expose to COM, add the following two statements:

[ComVisible( true )]
[ClassInterface( ClassInterfaceType.AutoDual )]

This class must be explicitly defined as public and have a public no-argument constructor, or COM won't register it.

In your AssemblyInfo.cs file, things that may cause problems are:

[assembly: CLSCompliant( true )]
Since this .dll is intended for COM interop, it should not be marked as CLS compliant.

[assembly: SecurityPermission( SecurityAction.RequestMinimum, Execution = true )]
[assembly: PermissionSet( SecurityAction.RequestOptional, Name = "Nothing" )]

Minimum permissions are great for security, but if your class does any file IO than these settings might cause permission errors.

Building the .NET class

Now, build your project.

Registering the .NET DLL

Regsvr32.exe registers COM libraries by loading the DLL and calling a self-registering method. This is how a COM object is installed onto the computer (registered into a spot in the registry). When you refer to the object's name, it goes to the registry to look for where the DLL actually resides and performs the call.

However, if you try to call regsvr32 on a .NET dll, you'll get the error message "DllName was loaded, but the DllRegisterServer entry point was not found. This file cannot be registered".

For managed libraries (such as the one you just wrote), you need to use RegAsm.exe instead of Regsvr32.exe to register your dll. RegAsm comes with the .NET SDK and can be found in your WINDOWS directory, in the most recent version folder under Microsoft.NET\Framework\. RegAsm creates a COM wrapper for a .NET assembly and adds the necessary entries to the registry, which allows COM clients to create .NET Framework classes as though the class were a COM class.

Open up a command prompt and type the following:

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\regasm.exe "PathToYourDLL\NameOfYourDLL" /codebase
(Where v2.0.50727 is replaced with your most current version of the .NET framework)

The /codebase argument writes the filepath for the assembly .DLL into the registry under HKEY_CLASSES_ROOT/CLSID/{some big number}/InprocServer32. If you did not check the "Sign the assembly" box in Visual Studio before building, you will see the following warning:

RegAsm warning: Registering an unsigned assembly with /codebase can cause your assembly to interfere with other applications that may be installed on the same computer. The /codebase switch is intended to be used only with signed assemblies. Please give your assembly a strong name and re-register it.

The advantage to using the /codebase switch is that the assembly can be stored in any directory, instead of having to be copied to the Global Assembly Cache. However, I've ignored this warning many times - the assembly is still registered.

If all goes well, you should see the message "Types registered successfully."

Running RegAsm: Types registered successfully

Notice what happens when I try to register my dll using an older version of the .NET framework:

Running RegAsm: Not a valid .NET assembly

RegAsm fails to load the dll "because it is not a valid .NET assembly" - probably because some of the .NET classes I'm using weren't available in older versions of the framework.

If you want, verify the dll was correctly written to the registry by typing regedit into a command prompt or into the "Run" box on the Start menu. The quickest way to find your dll is to right-click on the left side of the regedit console, select "Find...", then type the class' namespace into the search box. Your dll will appear in HKEY_CLASSES_ROOT and HKEY_LOCAL_MACHINE/Software/Classes. Both contain lists of Program IDs, or ProgIDs, which are strings like "Word.Document" or "Excel.Application", following the format "Namespace.ClassName". The ProgID of your dll should have a folder called "CLSID", for Class ID. The CLSID maps the class name to a Class ID to ensure classes with the same name are distinctly identified.

Using the .NET DLL

In your VB Script file, use CreateObject to create an instance of your .NET COM-interop class. CreateObject is a COM factory method that allows you to create instances of a class using only the ProgID, which is mapped to the CLSID in the registry. The ProgID will be "Namespace.ClassName". Calling CreateObject calls the default constructor of your class, which is why it is important that your class has a public default constructor.

The code will look something like this:

Sub FunctionName()
     Dim variableName
     Set variableName = CreateObject("Namespace.ClassName")
     variableName.CallSomeFunction()
End Sub