Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ActiveX EXE Wrappers

0.00/5 (No votes)
22 Nov 2006 1  
How to expose a .NET EXE assembly to a COM compliant client application (such as VB6 or VBScript) and force the client application to use the running instance of the .NET EXE assembly.

Problem

When I create a new application, I create the application in .NET. However, I do have many legacy applications that are still in production that were written with COM based languages (for example, VB6). To complicate matters further, many of the COM based applications were written as ActiveX EXE servers. I have multiple ActiveX EXE servers that talk to each other through their exposed COM interfaces. My goal is to migrate these applications to the .NET framework.

With .NET COM interoperability, it�s very easy to expose methods and properties to any COM compliant language such as VBScript or Visual Basic 6. In fact, with VB.NET applications and Visual Studio 2005, it becomes trivial: simply add a COM class to the VB.NET project and you�re done.

C# takes a little more effort to expose the assembly to COM � you need to create your own GUIDs and implement your own interfaces (you also need to use REGASM.exe to create a type library). Both .NET Class Libraries (DLLs) and Windows Applications (EXEs) can be exposed to COM. This article does not explain in detail how to expose methods and properties from a .NET assembly to COM. There are many great articles on CodeProject that explain this process in detail.

There is a problem that occurs when it comes time to invoke a .NET assembly through the exposed COM interface. It�s easy to invoke the assembly through the COM interface (for example, just use CreateObject(���) in VBScript or use the �New� keyword in VB6), however, you will quickly notice that there are problems:

  • The COM application does not use the running instance of the .NET assemby.

Additionally, the COM application loads the .NET assembly in its same address space. In the case of VBScript, every time you execute the script that calls the exposed COM interface, a new instance of the .NET assembly is loaded. When the script ends, so does the .NET assembly.

Problem Summary

How do we expose a .NET EXE assembly to a COM compliant client application (such as VB6 or VBScript) and force the client application to use the running instance of the .NET EXE assembly? If there is not a running instance of the .NET EXE assembly, then the client application should invoke a new instance of the .NET assembly.

Essentially, we are asking the .NET EXE assembly to behave like an ActiveX EXE server (out-of-process server) that we can late/early bind to from any COM compliant language.

Background

You may be asking yourself, �Why would we need a .NET assembly to mimic the behavior of an ActiveX EXE server?� The answer: The need to force a .NET EXE assembly to behave like an ActiveX EXE server is useful for upgrading legacy ActiveX EXE server applications to .NET assemblies in phases. I believe that all VB6 Windows applications should be upgraded to .NET. However, many of us do not have the luxury of upgrading every legacy application to .NET at the same time; the upgrades have to occur in phases. Also, some of your legacy applications may be written so they are exposed to a scripting language (VBScript or JavaScript). How can we continue to execute these scripts against the new/upgraded .NET framework assemblies?

To emphasis my point, consider the following example: Application A and Application B are enterprise applications that are installed on thousands of computers. Each application was written as a VB6 ActiveX EXE server application and each application �talks� to the other application through their exposed COM interfaces. Application B will be upgraded to .NET first (and put into production) followed by the upgrade of Application A. The communication between Application A and Application B cannot be broken during the upgrade process:

Solutions

Again, we are faced with the problem of upgrading a legacy VB6 ActiveX EXE server application to .NET and avoiding breaking the communication channels exposed to other legacy VB6 ActiveX EXE server applications. There are a number of solutions that can be employed to solve this issue. One potential solution would involve using TCP/IP listeners in the .NET assembly to listen on a port for incoming requests from client applications. A VB6 client application could then use WinSockets to �talk� to the .NET assembly. However, this solution would require changes in all the legacy VB6 applications that need to communicate with the upgraded .NET EXE assembly (not to mention re-testing and re-deployment of the legacy applications to thousands of computers).

In this article, I will explore creating an ActiveX EXE wrapper around the .NET EXE assembly. The benefit of using a wrapper around the new .NET EXE assembly is that none of the other legacy VB6 ActiveX EXE applications need to be modified (if the other VB6 application doesn�t use late binding, you have to make sure the COM interfaces in the wrapper are the same as the legacy ActiveX EXE that is being replaced).

The ActiveX EXE wrapper will be responsible for:

  1. Launching the .NET EXE assembly. The .NET EXE assembly will be loaded in the same address space as the ActiveX EXE wrapper.
  2. The ActiveX EXE wrapper will communicate with the .NET EXE assembly through exposed COM interfaces in the assembly.
  3. The ActiveX wrapper will also have exposed properties and methods that can be called by other COM applications.

Essentially, the ActiveX EXE wrapper will �bubble� COM requests it receives to the .NET EXE assembly.

Step 1: Create the VB.NET application that exposes COM methods

To illustrate the concept of ActiveX EXE wrappers, here�s a simple example. The VB.NET Windows Application is called �Message Net� and its interface appears as follows:

This is a very simple application that receives string messages and displays them in a list box. The application is composed of a form (frmMain) and two classes (App and ComExpose).

The App class simply holds a static/shared reference to frmMain:

Public Class App

    Public Shared MainForm As frmMain

End Class

The frmMain class obviously contains the code that creates the user interface. Additionally, frmMain contains the following code:

Private Sub frmMain_Load(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles MyBase.Load

    ' save a reference of this object in the App shared variable

    '   (if it's not already there!)

    If App.MainForm Is Nothing Then App.MainForm = Me

End Sub

And the button click code:

Private Sub Button1_Click(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles Button1.Click

ListBox1.Items.Insert(0, DateTime.Now.ToLongTimeString() & _
         " - Hello World!  This was added internally")

' Alternativly, we could use the ComExpose object

' for adding items directly to the list box


'Dim MyComExpose As ComExpose


'MyComExpose = New ComExpose()

'MyComExpose.DisplayMessage("Hello World!  This was " & _ 

'       "added internally through the ComExpose object")


 End Sub

That�s it; this is a complete (although not a very useful) application. Next, we need to expose the list box through a COM interface. As previously mentioned, VB.NET projects in Visual Studio 2005 allow you to add �COM Classes� (in the Solution Explorer, right click on the project, �Add, New Item�.� and select �COM Class�. An empty �COM Class� will be added to your project:

<ComClass(ComClass1.ClassId, ComClass1.InterfaceId, ComClass1.EventsId)> _
Public Class ComClass1

#Region "COM GUIDs"
    ' These  GUIDs provide the COM identity for this class 

    ' and its COM interfaces. If you change them, existing 

    ' clients will no longer be able to access the class.

    Public Const ClassId As String = _
           "3d9e2e6f-5bf2-4144-8790-4e5b0af104e9"
    Public Const InterfaceId As String = _
           "b89d94d5-2306-4afc-a3b5-e86a1958fb9d"
    Public Const EventsId As String = _
           "1e3a740b-f7a9-4d31-9a7d-db11210f0c01"
#End Region

    ' A creatable COM class must have a Public Sub New() 

    ' with no parameters, otherwise, the class will not be 

    ' registered in the COM registry and cannot be created 

    ' via CreateObject.

    Public Sub New()
        MyBase.New()
    End Sub

End Class

Any public methods and properties added to this class will be exposed to COM clients. Add the StartApplication() and DisplayMessages() methods to this new class

Public Sub StartApplication()

'**********************************************

' THIS METHOD MUST BE CALLED BY AN OUT

' OF PROCESS COM SERVER to start the .Net               

' application!!!!   

'**********************************************


    ' create the new form

    Dim frm As frmMain
    frm = New frmMain()

    ' save the reference to the form in a static variable

    App.MainForm = frm

    ' display some initial text in the list box

    frm.ListBox1.Items.Add(".Net application starting")

    ' show the form � needs to be shown modally.

    frm.ShowDialog()

End Sub

   
Public Sub DisplayMessage(ByVal Message As String)

'**************************************************

' this method displays the message from

' the parameter in the list box in 

' frmMain.    

'**************************************************


    If Not App.MainForm Is Nothing Then
        App.MainForm.ListBox1.Items.Insert(0, _
DateTime.Now.ToLongTimeString() & " - " & Message)
    End If

End Sub

One final step to finish our application and fully expose it to COM: Create a type library. The .NET framework comes with a utility called REGASM.exe that allows you to register assemblies that are exposed to COM. The REGASM utility also allows you to create a type library (TLB) that can be used in many programming IDEs (including VB6). To use REGASM, just run the Create.Bat file (you may need to update the paths in the bat file). Notice that MsgNet.tlb has been created.

Now that we have a .NET application that exposes COM methods, let's invoke the .NET assembly with VBScript (the script can be found in the file TestMsgNet.vbs).

Dim MyMsgNet

Set MyMsgNet = CreateObject("MsgNet.ComExpose")
MyMsgNet.StartApplication
MyMsgNet.DisplayMessage ("Hello from VBScript")
Set MyMsgNet = Nothing

As you can see from the test, it doesn�t work. The main interface and the initial message ".NET application starting" are displayed, however, the text �Hello from VBScript� is never displayed. Also note that every time you execute the above VBScript, the .NET application is reloaded.

This creates a problem for migrating applications to .NET. You could migrate every single ActiveX EXE server application to .NET at the same time, and utilize .NET Remoting as the communication tool between the various AppDomains, however, this approach is not realistic. Hence, the ActiveX EXE wrapper phased approach.

Step 2: Wrap the VB.NET project in an ActiveX server

The above solution is not very helpful. We want the .NET EXE assembly to behave like an ActiveX EXE server. That is, we want all client applications to utilize the running instance if available. To accomplish this goal, we need to wrap the .NET assembly with an ActiveX server. I understand that this is an additional step, however, it is a necessary step (otherwise, you need to change every ActiveX EXE server application, recompile, and deploy to utilize TCP/IP, for example). To complete this exercise, you need to use a programming language that will allow you to create an ActiveX EXE server. I will illustrate this using VB6.

  1. Create a new ActiveX EXE project in VB6:

  2. Add a reference to the �MsgNet.tlb� type library that you created with the REGASM utility.
  3. Create a module in the VB6 application and define a global variable called MyMsgNet. The module should also contain a �Sub Main� procedure to start the ActiveX EXE server:

    Option Explicit
    
    Public MyMsgNet As MsgNet.ComExpose
    
    Public Sub Main()
    
        Set MyMsgNet = New MsgNet.ComExpose
        MyMsgNet.StartApplication
                
    End Sub
    

    Note: you will have to set the �Startup Object� in the VB6 project to �Sub Main�.

    Also note, you will need to set the �Start Mode� to �Standalone�.

  4. Finally, to complete the ActiveX EXE wrapper for the .NET assembly, you need to create a VB6 class that will simply call the corresponding methods in the .NET assembly.
  5. Create a class in the VB6 project called �ComExpose� and add the following code:

    Option Explicit
    
    Public Sub DisplayMessage(ByVal Message As String)
    
        MyMsgNet.DisplayMessage Message
        
    End Sub

All done; the ActiveX EXE COM wrapper is complete. Compile the application. Notice that when you execute the VB6 ActiveX EXE wrapper, the .NET application is invoked.

Also note:

  • The Task Manager shows that both applications are executing: the .NET EXE assembly and the VB6 ActiveX EXE wrapper:

  • If the .NET EXE assembly is closed, the VB6 ActiveX EXE wrapper will also be closed.

Step 3: Testing the ActiveX EXE COM wrapper

Previously, we used a VBScript to test our .NET EXE assembly and found that the results were not good. The messages were never displayed in the .NET assembly. To test the new VB6 ActiveX EXE wrapper, we only need to make a small modification to our VBScript:

Dim MyMsgNet

Set MyMsgNet = CreateObject("MsgNet_Com.ComExpose")
MyMsgNet.DisplayMessage ("Hello from VBScript")
Set MyMsgNet = Nothing

We changed CreateObject(�MsgNet.ComExpose�) to CreateObject(�MsgNet_Com.ComExpose�) to switch from the COM interface in the .NET EXE assembly to the ActiveX EXE wrapper.

Notice that when you execute the VBScript now, it will utilize the running instance of the .NET EXE assembly. The goal has been accomplished: the .NET EXE assembly is �acting� like an ActiveX EXE server.

Conclusion

Using an ActiveX EXE wrapper, you can make a .NET assembly behave like an ActiveX EXE server. This is useful for upgrading legacy VB6 applications to .NET in phases or allowing the .NET assemblies to be extended with VBScript.

I can be reached at donaldsnowdy@hotmail.com.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here