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 operator | Objects 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
- Using Visual Studio 2005, right-click on the project name in Solution Explorer (or click "Project" in the menu) and go to "Properties...".
- On the Application tab, ensure the output type is "Class Library".
- On the Build tab, check the box labeled "Register for COM Interop".
- On the Signing tab, it's a good idea to check the box labeled "Sign the assembly"
if you're planning on releasing this .dll into the wild. If this assembly will be
shared amoung many applications on the same machine, this box MUST be checked.
A strong name key is a cryptographic signature which prevents someone from
taking over the name of your assembly (name spoofing) and guarantees that the
correct version of the assembly gets loaded.
Keven Kindt has created a strong name key file called "SagemMorphoInc.snk" which is available here as a zip file.
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."
Notice what happens when I try to register my dll using an older version of the
.NET framework:
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