Introduction
Typically, the available tools that provide interoperation between .NET code and VB6 code are based on proxies, wrappers or other forms of middleware that allow VB6 applications to call .NET Winforms. So far, we haven't seen a solution that allows us to do the opposite. That is, to embed VB6 forms in a .NET application. For that reason, we decided to create that solution on our own. At MasterSoft, we have many applications developed in Visual Basic 6 in which we invested several thousands of development hours. Right now we face the necessity of migrating them to .NET in a gradual way and with the least amount of effort.
To reach that goal, we envisioned a kind of interoperation between VB6 and .NET that would allow us to open VB6 forms in a .NET application. This would give us the opportunity to migrate the applications gradually, incorporating new functionality with "native" .NET code and maintaining intact � or as intact as possible � everything that's written in VB6. The main advantage of this strategy is in leveraging a .NET framework developed by ourselves, which gives us powerful tools for menu designing and security management, among many other things. All these benefits would be lost if we maintained the core of our applications in VB6.
The solution we applied is based on a .NET MDI environment from which we can open VB6 forms contained in a DLL, just as if they were MDI children of the .NET form. Instead of using toolkits or power packs for .NET-VB6 interop, we used instances of a .NET container form in which we embed the VB6 forms. By using API functions, the normal behavior of the embedded forms is simulated.
Implementation details
To use VB6 forms from within a .NET application, we opted to develop a "shell" (container) form in VB.NET with MDI child functionality. This creates an instance of the VB6 form through a specific class in the DLL ActiveX, which is ClsForms
. Using API functions and communication with the DLL, the VB6 form is instanced and embedded in the container.
Within the VB6 DLL, there is a public class that does the job of instancing the forms and communicating with the .NET application. This class acts as a controller of the instantiated forms in the DLL. It provides access to them through several methods that help the container to behave as the VB6 form would in a VB6 MDI container. Due to the fact that the VB6 forms reside in a DLL ActiveX, we needed to make sure that their MDIChild
property is set to False because we couldn't figure out how to change that property at runtime. In order to manage the events of the VB6 form, the .NET container uses a Timer control that constantly queries the VB6 class for messages to handle.
A big issue we needed to solve was the communication between VB6 forms. In many cases, our application forms call each other, passing parameters and results -- of searches, for instance -- between them through properties. In order to keep this behavior in the .NET application with the VB6 forms, it was necessary to give the VB6 forms the chance to create new instances of the container and embed in them other VB6 forms. This was done as explained below.
Showing the sequence
To make things a bit more encapsulated, we develop a FormController
class in the .NET project which maintains the instance of the DLL and creates multiple instances of the .NET container. Thus, we can open multiple forms using the same instance of ClsForms
.
Dim objFormController As New FormController(objMDIForm, "FormsVB6")
objFormController.OpenForm("frmChildForm")
The best way to show the interaction between objects is with this UML sequence diagram:
The OpenForm
function in the FormController
creates an instance of the ClsForms
class in the ActiveX DLL -- the one that contains all the VB6 forms -- and then instantiates the frmContainer
form. Finally, it calls its OpenForm
method. The OpenForm
method in the container in turn calls the OpenForm
method in ClsForms
. It does this in order to create a new instance of the form's name, passed by argument using the hidden VB6 Forms.Add("formname")
instruction.
When the form instance is created, the OpenForm
method returns the handle of the instance to the container. With that handle and through API functions, as well as through other methods of the ClsForms
class, the VB6 form is "drawn" inside the .NET container. The OpenForm
container obtains the property values of the VB6 form such as Title
, Width
, Height
, etc. in order to replicate them in the container, i.e. the GetWindowTitle
in the diagram above. The container form is also responsible for checking the state of its embedded form. In this way, it can know if the VB6 form has been closed by an Unload Me instruction and thus can close the container.
Form behavior
In order to mimic the event firing from the VB6 form, the container uses a Timer
control to constantly check if there are events in the VB6 form that need to be handled. We could have chosen to pass the events directly from the VB6 form to the .NET application, but that would have implied a much deeper modification of those forms' code, which was exactly what we wanted to avoid.
To give the VB6 form the ability to open "child" non-modal forms and keep its condition of MDIChild
, it is necessary to use the ShowForm
function in VB6 instead of the usual .Show method. The ShowForm
function does all the process needed to keep the called child form in the .NET environment within its container. That is basically all the work that needs to be done with the VB6 forms in order to embed them in a .NET container.
ShowForm(frmChildForm, True)
ShowForm(frmModalForm, False)
So we need only to replace the Show calls in the code to ShowForm()
, passing the form we want to show and a second boolean argument indicating if you want it to be an MDI child or not. If so, it calls the OpenForm
in the container by passing a message "OpenForm" to it. If not, it just opens the form as a regular modal form.
To preserve the normal behavior of the VB6 forms and their respective containers as MDI child forms � for instance, the normal use of Ctrl-Tab, the focus change between forms or the fact that the .NET MDI Form keeps active to open other .NET or VB6 forms � we needed to use various API functions like SendMessage
, SetForegroundWindow
and LockWindow
.
To enhance this technique, we are planning to add code to the intermediate class that sits between the .NET MDI form and the container. This would allow event firing from VB6 that affects the .NET environment. Examples would be modifying the Caption
property of the MDI Form, opening .NET "native" forms, etc.
References
We use a function from Stephen Kent to change the form border style at runtime.
Using the code
The code is pretty simple and is commented where I think it's needed. We didn't use advanced programming techniques, only a few API calls to do the "magic."
Conclusion
This way of interoperating between .NET and VB6 allows us to start a gradual migration path to .NET for our applications that won't force us to make immediate modifications to them. It also instantly adds many of the benefits of the .NET framework we developed to the applications. As an additional feature, this gradual migration path allows the end users to learn gradually how to use the new features that the .NET version of the application offers, without being forced to learn all the changes at once.
Special thanks go to Gustavo Du Mortier and Mariano Aranda from MasterSoft; they really help and colaborate with this.
History
- 27 June, 2007 -- Original version posted
- 12 July, 2007 -- Article edited and moved to the main CodeProject.com article base