By Elena Lawrence


2011-08-17 12:12:10 8 Comments

How do I make a C# DLL into a COM interop DLL that can be consumed by a VB6 application?

5 comments

@Dietrich Baumgarten 2019-08-20 12:08:30

Most examples in the internet of COM servers contain only one CoClass, and it is claimed that this CoClass must have a public constructor. This is true in this case, but normal servers have more than one CoClass, of which only one can be created, whereas the instances of the non-creatable CoClasses are properties of the creatable CoClass. For example, consider the Word object model with the creatable CoClass Application that has the Documents property which in turn consists of instances of the CoClass Document. The following server has two CoClasses, one with a public constructor and one with a private constructor.

  1. Create a solution for a C# Class Library (.Net Framework), not Class Library (.Net Standard) and name it for example BankServerCSharp. Choose this name wisely, because it will be the main part of the ProgIDs of your CoClasses and the namespace name in C++. This name will also be listed in the References dialog box of C# and VBA.

  2. Delete the boilerplate code and add two files Bank.cs and Account.cs. Insert the following code:

    //Account.cs
    using System.Runtime.InteropServices;
    
    namespace BankServerCSharp
    {
      [ComVisible(true)]  // This is mandatory.
      [InterfaceType(ComInterfaceType.InterfaceIsDual)]
      public interface IAccount
      {
        double Balance { get; } // A property
        void Deposit(double b); // A method
      }
    
      [ComVisible(true)]  // This is mandatory.
      [ClassInterface(ClassInterfaceType.None)]
      public class Account:IAccount
      {
        private  double mBalance = 0;
        private Account() { }     // private constructor, coclass noncreatable
    
        public static Account MakeAccount() { return new Account(); }
        //MakeAccount is not exposed to COM, but can be used by other classes
    
        public double Balance  { get {  return mBalance; } }
        public void Deposit(double b) { mBalance += b; }
      }
    }
    
    //Bank.cs
    using System.Runtime.InteropServices;
    
    namespace BankServerCSharp
    {
      [ComVisible(true)]  // This is mandatory.
      [InterfaceType(ComInterfaceType.InterfaceIsDual)]
      public interface IBank
      {
        string BankName  {  get;  set;  }      // A property
        IAccount FirstAccount { get; }         // Another one of type IDispatch
      }
    
      [ComVisible(true)]  // This is mandatory.
      [ClassInterface(ClassInterfaceType.None)]
      public class Bank:IBank
      {
        private string Name = "";
        private readonly Account First;
    
        public Bank() { First = Account.MakeAccount(); }
    
        public string BankName  {
          get {   return Name; }
          set {   Name= value; }
        }
    
        public IAccount FirstAccount  {
          get { return First; }
        }
      }
    }
    
  3. Build the project with the configuration Release/Any CPU. The output is the managed DLL BankServerCSharp.dll located in the \bin\release folder.

  4. Now you must register your managed COM DLL. Don't try regsvr32, there is a special program called regasm for managed COM DLLs. Regasm has a version for 32-bit and for 64-bit apps. Open a command prompt as administrator and change to C:\Windows\Microsoft.NET\Framework\v4.0.30319. This folder contains the regasm.exe app to register the managed COM DLL as if it would be a native 32-bit COM DLL.

  5. Type RegAsm.exe /tlb /codebase path_to_your_bin_release_folder\BankServerCSharp.dll. You must register your DLL on any computer in this way. Don’t forget the /tlb switch that creates the type library. The compiler will comment the switch /codebase with some warnings that you can ignore. The DLL is registered in the WoW64 part of the registry and can be used by native (unmanaged) 32-bit apps.

  6. Now repeat the registration for usage of the managed COM DLL by 64-bit apps. Change to C:\Windows\Microsoft.NET\Framework64\v4.0.30319 and type the same command as before.

  7. You can speed up the registration on your own PC by running Visual Studio with administrative rights and adding the following post-build events:

    %SystemRoot%\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe /tlb /codebase "$(TargetPath)"
    %SystemRoot%\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe /tlb /codebase "$(TargetPath)"
    

You can now use your DLL like a native unmanaged COM DLL. Test your DLL with VBA: Under Tools/References tick BankServerCSharp. If it is not shown, the registration failed. A simple test sub:

Sub TestSOExampleNew()
 On Error GoTo Oops

   Dim BiBiBaBa As New BankServerCSharp.Bank 'New!
   BiBiBaBa.BankName = "Big Bird Bad Bank"
   Dim Account As BankServerCSharp.Account   'No New!
   Set Account = BiBiBaBa.FirstAccount
   Account.Deposit 2000
   MsgBox BiBiBaBa.BankName & ". First client's balance: " & Account.Balance

   Exit Sub

 Oops:
   MsgBox "Sorry, an unexpected error occurred!"
End Sub

To test your managed COM DLL in C++, create a new Console Application, insert the following code and build as Release/x64 or Release/x86:

#include "stdafx.h"
#import "D:\Aktuell\CSharpProjects\BankServerCSharp\BankServerCSharp\bin\Release\BankServerCSharp.tlb"
//this is the path of my C# project's bin\Release folder

inline void TESTHR(HRESULT x) { if FAILED(x) _com_issue_error(x); };

int main()
{
  try
  {
    TESTHR(CoInitialize(0));
    BankServerCSharp::IBankPtr BankPtr = nullptr;
    TESTHR(BankPtr.CreateInstance("BankServerCSharp.Bank"));
    BankPtr->BankName = L"Ernie First Global Bank";
    BankServerCSharp::IAccountPtr AccountPtr = BankPtr->FirstAccount;
    TESTHR(AccountPtr->Deposit(200.09));
    wprintf(L"Name: %s, Balance: %.2f\n", (LPCWSTR)BankPtr->BankName, AccountPtr->Balance);
  }
  catch (const _com_error& e)
  {
    CStringW out;
    out.Format(L"Exception occurred. HR = %lx, error = %s", e.Error(), e.ErrorMessage());
    MessageBoxW(NULL, out, L"Error", MB_OK);
  }

  CoUninitialize();// Uninitialize COM
  return 0;
}

@Matas Vaitkevicius 2017-04-06 03:50:54

As en extension to @Kieren Johnstone's answer a practical code example on class modifications you need to do:

From:

public class ApiCaller 
{

    public DellAsset GetDellAsset(string serviceTag, string apiKey)
    {
     ....
    }
}

public class DellAsset
{
    public string CountryLookupCode { get; set; }
    public string CustomerNumber { get; set; }
    public bool IsDuplicate { get; set; }
    public string ItemClassCode { get; set; }
    public string LocalChannel { get; set; }
    public string MachineDescription { get; set; }
    public string OrderNumber { get; set; }
    public string ParentServiceTag { get; set; }
    public string ServiceTag { get; set; }
    public string ShipDate { get; set; }

}

To:

[Guid("EAA4976A-45C3-4BC5-BC0B-E474F4C3C83F")]
[ComVisible(true)]
public interface IComClassApiCaller
{
    IComClassDellAsset GetDellAsset(string serviceTag, string apiKey);

}

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

[Guid("0D53A3E8-E51A-49C7-944E-E72A2064F938"),
    ClassInterface(ClassInterfaceType.None),
    ComSourceInterfaces(typeof(IComClassApiCallerEvents))]
[ComVisible(true)]
[ProgId("ProgId.ApiCaller")]
public class ApiCaller : IComClassApiCaller {

    public IComClassDellAsset GetDellAsset(string serviceTag, string apiKey)
    {
        .....
    }
}


[Guid("EAA4976A-45C3-4BC5-BC0B-E474F4C3C83E")]
[ComVisible(true)]
public interface IComClassDellAsset
{
     string CountryLookupCode { get; set; }
     string CustomerNumber { get; set; }
     bool IsDuplicate { get; set; }
     string ItemClassCode { get; set; }
     string LocalChannel { get; set; }
     string MachineDescription { get; set; }
     string OrderNumber { get; set; }
     string ParentServiceTag { get; set; }
     string ServiceTag { get; set; }
     string ShipDate { get; set; }

}

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

[Guid("0D53A3E8-E51A-49C7-944E-E72A2064F937"),
    ClassInterface(ClassInterfaceType.None),
    ComSourceInterfaces(typeof(IComClassDellAssetEvents))]
[ComVisible(true)]
[ProgId("ProgId.DellAsset")]
public class DellAsset : IComClassDellAsset
{
    public string CountryLookupCode { get; set; }
    public string CustomerNumber { get; set; }
    public bool IsDuplicate { get; set; }
    public string ItemClassCode { get; set; }
    public string LocalChannel { get; set; }
    public string MachineDescription { get; set; }
    public string OrderNumber { get; set; }
    public string ParentServiceTag { get; set; }
    public string ServiceTag { get; set; }
    public string ShipDate { get; set; }

}

Hope this saves you some time

@Panzercrisis 2017-06-23 21:07:54

Thanks for the example! How would you be able to call GetDellAsset in VB6 if you're trying to access the class through CreateObject?

@Matas Vaitkevicius 2017-06-24 01:51:54

@Panzercrisis Hi, take a look at this stackoverflow.com/questions/345178/…

@bhupesh 2016-07-20 18:06:59

COM Interop is a technology included in the .NET Framework Common Language Runtime (CLR) that enables Component Object Model (COM) objects to interact with .NET objects, and vice versa. COM Interop aims to provide access to the existing COM components without requiring that the original component be modified and vice-versa. More at: http://www.writeulearn.com/com-interop-using-csharp/

@Kieren Johnstone 2011-08-17 12:17:27

This is the answer I wanted to find in StackOverflow but couldn't. It turns out to be fairly easy to turn a simple C# dll into a COM dll.

To create the C# dll

Create a solution with a C# class project. The class should have an interface for the properties/methods and an interface for the events. Assign GUID attributes to the class and interfaces as described in MSDN - Example COM Class (C# Programming Guide). Also see: MSDN - How to: Raise Events Handled by a COM Sink.

In Project Properties > Application tab > Assembly Information button > check "Make assembly COM-Visible". This makes all public methods in the class COM visible.

In Project Properties > Build tab > Set "Platform target" to x86.

That's all you need to do to create the DLL. To call the DLL, you need to register it.

Registering the DLL on your development machine

You can register the DLL one of these ways:

  • Check Project Properties > Build Tab > "Register for COM Interop". This will automatically register the DLL when you build it.
  • Manually register the DLL with RegAsm. This allows you to register the DLL in the directory of your choice, rather than in the build directory. This is the method I used.

    • Do not check Project Properties > Build Tab > "Register for COM Interop"
    • Copy the DLL to the directory where you want to register it
    • Open a command shell with administrator rights and type

      RegAsm.exe -tlb -codebase mydll.dll
      

      RegAsm.exe can be found in "C:\Windows\Microsoft.NET\Framework\v2.0.50727", while "mydll.dll" is the name of your DLL; tlb means "create a type library"; codebase means "write the directory location to the Registry, assuming it is not being placed in the GAC".

      RegAsm will display a warning that the assembly should be strong-named. You can ignore it.

      At this point, you should be able to add a reference to the COM DLL in VB6, see it with Intellisense, and run it just like a regular COM DLL.

Installing the DLL with InstallShield

If you are using InstallShield to install the DLL along with the rest of your application, do the following.

In InstallShield, add a new Component to the Components list. Remember to associate the Component with a Feature. Set component property ".NET COM Interop" to Yes.

Add the .dll file to the Files section of the Component. Do not check the "Self-Register" property. Right-click on the .dll file and select "Set Key File".

Add the .tlb file to the Files section of the Component. Check the "Self-Register" property.

The correct version of the .Net Framework needs to exist on the target PC.

That's it.

@Beth Whitezel 2011-10-28 17:12:48

I am using InstallSheild 11.5 and I do not see the ".NET COM Interop" option. The help file that 11.5 comes with does say it should be there so I don't know if I am just looking in the wrong view or what. Could you update your answer to include where that property is set?

@Elena Lawrence 2011-11-04 20:46:44

Using InstallShield 2011, in the Organization/Components tree there is a section for .NET Settings, which includes the .NET COM Interop setting.

@Rinku 2017-04-15 13:50:41

your answer really helpful.

@Ewerton 2017-05-22 11:07:19

When i try to reference the DLL in the VB6 IDE i receive error: "Can't add a reference to the specified file". Should i reference the DLL or the TLB file?

@Rahul Kishore 2017-06-05 02:54:52

Awesome answer, I missed the part on -tlb -codebase. Wrecked my head over this for a few hours until I found this. Thanks!

@Darthchai 2017-10-19 00:15:11

Excellent answer. The key for me was "Make assembly COM-Visible" which I haven't seen in any other online tutorial for this process. Great work. Thanks

@Zhang 2018-08-07 00:56:25

warning MSB3214: "……\CsComLib.dll" does not contain any types that can be registered for COM Interop.

@Zhang 2018-08-07 00:56:50

error MSB3213: Cannot register type library "……\CsComLib.tlb". Error accessing the OLE registry.

@Zhang 2018-08-07 03:12:49

regsvr32 entry-point was not found

@pcunite 2015-07-31 15:58:18

Microsoft has a quick how to here. However, you'll need to register the dll. To make your C# dll more like a simple C dll (C# Unmanaged Exports), use this method, which gets described in detail here.

Related Questions

Sponsored Content

42 Answered Questions

[SOLVED] How do I create an Excel (.XLS and .XLSX) file in C# without installing Microsoft Office?

  • 2008-09-29 22:30:28
  • mistrmark
  • 1045617 View
  • 1799 Score
  • 42 Answer
  • Tags:   c# .net excel file-io

39 Answered Questions

24 Answered Questions

[SOLVED] Reading settings from app.config or web.config in .NET

31 Answered Questions

[SOLVED] How can I generate random alphanumeric strings?

  • 2009-08-27 23:07:24
  • KingNestor
  • 652454 View
  • 885 Score
  • 31 Answer
  • Tags:   c# .net random

45 Answered Questions

[SOLVED] Deep cloning objects

  • 2008-09-17 00:06:27
  • NakedBrunch
  • 772801 View
  • 2125 Score
  • 45 Answer
  • Tags:   c# .net clone

27 Answered Questions

[SOLVED] How to cast int to enum?

  • 2008-08-27 03:58:21
  • lomaxx
  • 1271755 View
  • 2998 Score
  • 27 Answer
  • Tags:   c# enums casting

41 Answered Questions

[SOLVED] How do I properly clean up Excel interop objects?

27 Answered Questions

[SOLVED] How to enumerate an enum?

10 Answered Questions

[SOLVED] Interop type cannot be embedded

Sponsored Content