Archive for April, 2009

Call A C# Assembly From VB6

I have had to figure this out twice now, so I will write it down this time: how to call and debug a c# assembly from VB6 in a development environment. The first thing you have to do is set up your c# assembly so that it is exposed as a COM object. This is more or less covered here, and I am sure elsewhere, but by way of an example, let’s create a .NET assembly that reverses strings (For this purpose, we will use some techniques outlined by Mladen Prajdić at his I want some Moore blog).

I will create a String.Utilities project with a single public Reverser class. This class is implemented as follows:

   1: using System;  
   2: using System.Collections.Generic;  
   3: using System.Text;  
   4: using System.Runtime.InteropServices;  
   5:   
   6: namespace String.Utilities {  
   7:       
   8:     // ClassInterfaceType.AutoDual allows early-bound  
   9:     // COM clients and compile-time checking. Note that  
  10:     // it completely ignores COM versioning, and that   
  11:     // you should recompile your COM clients every time  
  12:     // this .NET component is changed.  
  13:     [ClassInterface(ClassInterfaceType.AutoDual)]  
  14:     public class Reverser {  
  15:   
  16:         // Types must have a public default constructor   
  17:         // to be instantiated through COM. Managed public  
  18:         // types are visible to COM, but without a public  
  19:         // default constructor (a constructor without   
  20:         // arguments), COM clients cannot create an   
  21:         // instance of the type.  
  22:         public Reverser() {  
  23:         }  
  24:   
  25:         // We use this attribute to specify which public  
  26:         // methods will be visible to COM.  The default   
  27:         // value is true for public methods, so this is  
  28:         // not really necessary!  
  29:         [ComVisible(true)]  
  30:         public string Reverse(string str) {  
  31:             // This method is from Mladen Prajdić's   
  32:             // I want some Moore blog:  
  33:             // http://weblogs.sqlteam.com/mladenp/archive/2006/03/19/9350.aspx  
  34:               
  35:             char[] charArray = str.ToCharArray();  
  36:             int len = str.Length - 1;  
  37:   
  38:             for (int i = 0; i < len; i++, len--) {  
  39:                 charArray[i] ^= charArray[len];  
  40:                 charArray[len] ^= charArray[i];  
  41:                 charArray[i] ^= charArray[len];  
  42:             }  
  43:             return new string(charArray);  
  44:         }  
  45:   
  46:         [ComVisible(true)]  
  47:         public string BetterReverse(string x) {  
  48:             // This method is from Mladen Prajdić's   
  49:             // I want some Moore blog:  
  50:             // http://weblogs.sqlteam.com/mladenp/archive/2006/03/19/9350.aspx  
  51:   
  52:             char[] charArray = new char[x.Length];  
  53:             int len = x.Length - 1;  
  54:             for (int i = 0; i <= len; i++)  
  55:                 charArray[i] = x[len - i];  
  56:             return new string(charArray);  
  57:         }  
  58:     }  
  59: }  

Code is interop ready, now we just need to set up some project settings. First, we have to update (or add) the ComVisible setting in the c# project’s AssemblyInfo.cs file (this is the thing I always forget to do):

   1: // Setting ComVisible to false makes the types in this assembly not visible  
   2: // to COM components.  If you need to access a type in this assembly from  
   3: // COM, set the ComVisible attribute to true on that type.  
   4: [assembly: ComVisible(true)] 

Next, open the project’s properties and click on the Build tab. Ensure the Register for COM interop option is selected:

buildoption

In order to be able to debug this while running the VB6 client from the development environment, you want to go to the Debug tab and select the Start external program option specifying the path to the VB6.exe.

Now we can build the assembly. When we debug, as specified in the debug options, VB6 will start. You can then open an existing project (or create a new one) that you want to use as a client for the .NET service. Click on the (VB6’s) project references and select the .NET assembly (which is now exposed as a COM object):

vb6ref

Here is my VB6 client code for calling the .NET assembly:

   1: Private Sub cmdReverse_Click()  
   2:   
   3: ' Notice how the dot (".") came across as  
   4: ' an underscore!  
   5: Dim aReverser As String_Utilities.Reverser  
   6: Dim reversed As String  
   7:   
   8: ' Instantiate the .NET Reverser object  
   9: Set aReverser = New String_Utilities.Reverser  
  10:   
  11: ' Call a method in the assembly  
  12: reversed = aReverser.Reverse(txtString.Text)  
  13:   
  14: ' Show the user the text in reverse  
  15: lblReverse.Caption = reversed  
  16:   
  17: End Sub

Running the VB6 client and clicking the button gives the following:

revsample

Of course, within the VB6 environment you can fully debug the client (set breakpoints, add watches, etc.). Now, run the VB6 project again, but this time set a breakpoint at public string Reverse(string str) in the Reverser class in VS2005. When you click the button, you will break at the Reverse method in the .NET debugging environment:

debugsample

Therefore we can call an assembly from a VB6 application and debug both sets of code at the same time. A couple things: (1) the assembly is only registered as a COM object for the lifetime of the debugging session initiated from VS2005. So you cannot open VB6 and debug on its own without building and registering the assembly as a COM object (you use the regasm utility). (2) there is a better way than using ClassInterfaceType.AutoDual to expose the class in COM which I will cover another day.