Click here to Skip to main content
15,868,141 members
Articles / Programming Languages / VBScript
Article

ActiveX EXE Wrappers

Rate me:
Please Sign up or sign in to vote.
3.65/5 (13 votes)
22 Nov 2006CPOL9 min read 110.4K   1.3K   58   10
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.

Image 1

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:

Image 2

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:

Image 3

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:

VB
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:

VB
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:

VB
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:

VB
<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

VB
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).

VBScript
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:

    Image 4

  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:

    VB
    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”.

    Image 5

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

    Image 6

  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:

    VB
    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:

    Image 7

  • 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:

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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
Developer

Comments and Discussions

 
QuestionRegarding ActiveX EXE Pin
Member 1194059112-Dec-18 20:20
Member 1194059112-Dec-18 20:20 
GeneralMy vote of 4 Pin
Jet_Zeng24-Jun-13 23:26
Jet_Zeng24-Jun-13 23:26 
QuestionSimple dirty way... Pin
Vozzie212-Mar-13 18:54
Vozzie212-Mar-13 18:54 
Question.Net Framework Required? Pin
sjbarber37322-Feb-10 10:36
sjbarber37322-Feb-10 10:36 
General.net to .net assembly Pin
pierpaolo paparo22-Aug-09 14:11
pierpaolo paparo22-Aug-09 14:11 
GeneralActiveX EXE in .NET way Pin
ip26625-Dec-08 16:14
ip26625-Dec-08 16:14 
QuestionFollow up from direct email about ways forward from vb6 to .net Pin
maj309121-Jul-08 3:12
maj309121-Jul-08 3:12 
Hi Donald,

I'm just following up on my direct email to you, albeit a bit delayed.

Below is our email trail in reverse order:

I look forward to any advice you can offer in helping to move this migration forward.

Regards,

Mark J.

------------------------------------------------

The “Top Level Client Access” (i’ll call it Kernel from now on) only uses sockets for accepting connections from external device clients (wireless terminals or terminal emulators). At design time a reference to the ActiveX EXE is added to the project, so we can use standard “Set objClient1 = new objClientHandler” type notation for creating an instance of it.

It then creates an instance of the “ActiveX EXE Client Handler” which exposes methods and properties to the Kernel for communications (so I guess this is COM??) to service each device connected to the kernel.

The lower level stuff is pretty much as you described.

So to recap, the kernel handles connections from multiple “devices” using windows sockets. For each “device” that connects to the kernel, a new ActiveX EXE Client is created, which can then access the business services.

------------------------------------------------

No problem Mark,

Just for clarification....the "Top Level Client" (the VB6 standard EXE) opens a TCP/IP socket to the Client Hander and sends "requests" to the Client Handler. It then waits for a response from the Client Handler. The Client Handler then invokes the Business Services and passes the client's request. Finally, the business Service calls the Data Access layer. Eventually, the "response" data is passed back to the "Top Level Client" and the client closes the TCP/IP socket.

Did I state the flow correctly? If so, can you post your question in the "comment" section of my code project article and I will respond to the question there?

Thanks,

Donald.

------------------------------------------------

Hi Donald,

I was reading your article on CodeProject after doing a google search.

I hope you don't mind me emailing you about it, but I wondered if you could offer a little bit of advice on migrating a VB6 application to .Net as you seem to understand things a lot more than what I do.

My current application in VB6 is a multi-tier application with the structure below:


Top Level Client Access - Standard EXE
|
Client Handler - ActiveX EXE
|
Business Services - ActiveX DLL
|
Data Access - ActiveX DLL


The top level program allows multiple client connections using Windows Sockets on a single port. For each connections, an instance of the Client Handler (ActiveX EXE) is created and comms is handled between the Top Level and Client Handler using COM I believe.

I'd appreciate it if you could give me a rough idea or point me in the right direction on how or where I would start in porting this type of application to .Net.

Thanks in advance for any advice you can offer.

Kind regards,

Mark Johnson
GeneralActiveX Exe without wrapper Pin
Markus Schmidt26-Nov-06 20:29
Markus Schmidt26-Nov-06 20:29 
AnswerRe: ActiveX Exe without wrapper Pin
DonSn27-Nov-06 4:39
DonSn27-Nov-06 4:39 
QuestionRe: ActiveX Exe without wrapper Pin
Rapidforce15-Jan-09 12:07
Rapidforce15-Jan-09 12:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.