What we've decided to do is use C# to write new class objects that we need and use these from VB6. As we get time and find that we can make improvements to the VB6 versions of existing classes, we'll replace those objects with new objects written in C#. We'll try to use C# User Controls for new GUI elements. What this does is allow us to develop new components in C# and slowly migrate existing code into C#, all while still releasing new versions. As we get more pieces written in C# we can later make the full transition and move away from VB6 completely.
For this to work, we need to be able to do two things:
- Create class objects in C# that have properties, methods, and events that we can use from VB6.
- Create user controls in C# that we can place on VB6 forms, with properties, methods, and events.
I found that there are tons of web pages that talk about COM Interop from .NET, but very few that have step-by-step examples of what you need to do to actually get it to work.
For my first entry, I'll go through the steps for making a COM object in C# that you can directly use from VB6, written in Visual Studio 2008. If there's interest, I'll step through how we're creating UserControls in C# that we can put on VB6 forms.
Create the C# Project
-
Open VisualStudio 2008 and create
a new C# Class Library Project. Change the name to something
meaningful. The project name, just as in VB6, will become the first
part of the ProgID. In this example I’ll use CSharpVB6Test.
Click Ok to create your project.
-
Right-click on the Project note of
the Solution Explorer, click “Properties”, click the “Build”
tab, and check the “Register for COM interop” checkbox.
-
Click on the Signing
tab, check the “Sign the assembly” checkbox. Select “<New…>”
from the dropdown list, put the project name (or any other string)
in the key file name box, and optionally enter a password. If you
choose not to use a password (I didn’t for this sample), uncheck
that box.
- Click “OK” and close the Project Properties tab.
- Properties & Methods: Right-click the project in the Solution explorer, Click “Add”, “New Item”, select “Interface” as the type, and give it a name. For this example I’ll use “IMyInterface.cs”. Click the “Add” button.
- At the top of the new interface file, add the using statement for Interop services:
using
System.Runtime.InteropServices;
- Above your interface declaration, add the following two lines:
[ComVisible(true)]
[Guid("########-####-####-####-############")]
- Run the guidgen.exe program (Start -> All Programs -> Microsoft Visual Studio 2008 -> Visual Studio Tools -> Visual Studio 2008 Command Prompt, then "guidgen" at the command prompt). Click New GUID, select the Registry Format, and click the Copy button. Replace the guid text with the text in your clipboard, removing the braces {} from the guid.
- Add the “public” keyword to the interface.
public
interface
IMyInterface
- Define your interface. For this example I’ll declare a method “MyMethod” that returns a string. For each method, you need to add a COM dispatch identifier. These can be sequential numbers:
[DispId(1)]
string
MyMethod();
- The final interface then looks something like this:
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
using
System.Runtime.InteropServices;
namespace
CSharpVB6Test
{
[ComVisible(true)]
[Guid("########-####-####-####-############")]
public
interface
IMyInterface
{
[DispId(1)]
string
MyMethod();
}
}
- Events: Right-click the project in the Solution explorer, Click “Add”, “New Item”, select “Interface” as the type, and give it a name. For this example I’ll use “IMyEvents.cs”. Click the “Add” button.
- At the top of the new interface file, add the using statement for Interop services:
using
System.Runtime.InteropServices;
- Above your interface declaration, add the following three lines:
[ComVisible(true)]
[Guid("########-####-####-####-############")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
- Run the guidgen.exe program (Start -> All Programs -> Microsoft Visual Studio 2008 -> Visual Studio Tools -> Visual Studio 2008 Command Prompt, then "guidgen" at the command prompt). Click New GUID, select the Registry Format, and click the Copy button. Replace the guid text with the text in your clipboard, removing the braces {} from the guid.
- Add the “public” keyword to the interface.
public
interface
IMyEvents
- Define your events. For this example I’ll declare a method “MyStatusEvent” that passes a string. For each event, you need to add a COM dispatch identifier. These can be sequential numbers:
[DispId(1)]
void
MyStatusEvent(string
status);
- The final event interface then looks something like this:
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
using
System.Runtime.InteropServices;
namespace
CSharpVB6Test2
{
[ComVisible(true)]
[Guid("########-####-####-####-############")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public
interface
IMyEvents
{
[DispId(1)]
void
MyStatusEvent(string
status);
}
}
- Right-click the Class1.cs node in the Solution explorer, add rename it to something meaningful. For this example I’ll rename it to MyClass.cs. Double click the file to open the code window.
- At the top of the class file, add the using statement for Interop services:
using
System.Runtime.InteropServices;
- Above your class declaration, add the following four lines:
[ComVisible(true)]
[Guid("########-####-####-####-############")]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(IMyEvents))]
- Run the guidgen.exe program (Start -> All Programs -> Microsoft Visual Studio 2008 -> Visual Studio Tools -> Visual Studio 2008 Command Prompt, then "guidgen" at the command prompt). Click New GUID, select the Registry Format, and click the Copy button. Replace the guid text with the text in your clipboard, removing the braces {} from the guid.
- Add a public event handler and a public event that matches your event declaration in the event interface class. You’ll use this event within your code to raise events to VB6.
public
delegate
void
MyStatusEventHandler(string
status);
public
event
MyStatusEventHandler
MyStatusEvent;
- Add the properties/methods interface to the class definition:
public
class
MyClass
: IMyInterface
-
Hover over [IMyInterface]
until you have the dropdown menu option, and select ‘Implement
Interface IMyInterface:
- Replace the “throw new NotImplementedException()” to raise our event and return some text:
#region
IMyInterface Members
public
string
MyMethod()
{
if
(MyStatusEvent != null)
{
MyStatusEvent("some
status message");
}
return
"it
works!";
}
#endregion
- The final test class will look something like this:
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
using
System.Runtime.InteropServices;
namespace
CSharpVB6Test2
{
[ComVisible(true)]
[Guid("########-####-####-####-############")]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(IMyEvents))]
public
class
MyClass
: IMyInterface
{
public
delegate
void
MyStatusEventHandler(string
status);
public
event
MyStatusEventHandler
MyStatusEvent;
#region
IMyInterface Members
public
string
MyMethod()
{
if
(MyStatusEvent != null)
{
MyStatusEvent("some
status message");
}
return
"It
works!";
}
#endregion
}
}
- Click “File” -> “Save All”, specify a directory in which to save your solution, and click “Save”
- Build your solution: “Build” -> “Build Solution”
-
Open a command prompt (Start ->
Run -> cmd.exe) and change directory to the Release folder where
your new dll is stored.
- Run the regasm.exe command, adding an option to generate a type library against your new dll.
- regasm.exe CSharpVB6Test.dll /tlb
You
should see something like this:
Microsoft
(R) .NET Framework Assembly Registration Utility 2.0.50727.3053
Copyright
(C) Microsoft Corporation 1998-2004. All rights reserved.
Types
registered successfully
Assembly
exported to 'C:\Documents and Settings\rphillips\My Documents\Visual
Studio
2008\Projects\CSharpVB6Test\CSharpVB6Test\bin\Release\CSharpVB6Test.tlb',
and the type library was registered successfully
- Open a new VB6 “Standard EXE” project.
-
Open “Project” ->
“References”, and navigate to your project name in the list.
Check it and click OK.
- You can verify that the interfaces worked properly by looking at the object browser. You should see the methods, properties, and events declared in the interface classes.
- In your code, create a new instance of the C# class object with events. Add a MsgBox to the event and test the method.
Option
Explicit
Dim
WithEvents oCSharpObject As CSharpVBTest.MyClass
Private
Sub Form_Load()
Set
oCSharpObject = New CSharpVBTest.MyClass
Debug.Print
"Method results: " & oCSharpObject.MyMethod
End
Sub
Private
Sub Form_Unload(Cancel As Integer)
Set
oCSharpObject = Nothing
End
Sub
Private
Sub oCSharpObject_MyStatusEvent(ByVal status As String)
MsgBox
status
End
Sub
- Click run and see that your C# object works.
Some things to watch for
Making changes to your C# DLL
I’ve noticed that in some cases, new
methods may not be added automatically or old methods may not be
removed as they should. When making new versions of your C# DLL you
should always unregister the COM properties prior to recompiling.
This is done by adding the “/unregister” option to the regasm.exe
call.
- regasm /unregister CSharpVB6Test.dll /tlb:CSharpVB6Test.tlb
Change the C# code, rebuild, and
register again. I saw this happening another time, and it seemed to
fix itself when I increased the assembly build version. Good luck
with this one ;)
Unable to copy file "obj\Release\???.dll" to "bin\Release\???.dll". The process cannot access the file because it is being used by another process.
When you’ve made a reference to the
DLL from VB6, you won’t be able to recompile a new DLL until you
close the VB6 IDE.
My methods and properties aren’t available through Intellisense.
Something didn’t go right in the C#
class declarations. Go back and review each of the steps and make
sure you didn’t miss something. This can be anything from not
making the interface public to forgetting one of the class modifiers.
I see methods like “Equals”, “GetHashCode”, “GetType”, “MemberwiseClone”, or “ToString” and not the interface methods I declared.
See the above note.
What if I have implemented more than one interface in my class object?
This gives interesting results. The
first interface that your class object implements will become its
default interface. The other interface(s) will then be added to the
COM wrapper as class definitions that can’t be created (similar to
creating an ActiveX DLL class and setting the Instancing property to
“2 – PublicNotCreatable”. You can then create the C# object
using the default interface, and then cast it to the other
interfaces. For instance, if I added a second interface to my C#
project my VB6 code might look like this:
Option
Explicit
Private
Sub Form_Load()
Dim
o As New CSharpVB6Test.MyClass
Dim
o2 As CSharpVB6Test.IAnotherInterface
Set
o2 = o
MsgBox
o2.MyOtherMethod
End
Sub
I’m getting an error: Automation error -
The system cannot find the file specified.
I’ve usually encountered this when
changing the C# class definition. Remove the reference, unregister
the assembly, recompile, re-register, add the reference. Check that
the Dispatch event name matches the name in the class object. This
has only happened a few times and I can’t pinpoint a solution..
keep messing with it.






Good information about C# class available to VB6.
ReplyDeleteVB6 to C#