Deploying Interop UserControls with RegFree-COM

One of the features of the latest Interop Forms Toolkit is the ability to develop UserControls in addition to Windows Forms. In a previous post, I showed how to create a simple Interop UserControl. I also have a video here on creating them, and one here on deploying a hybrid application using the standard Setup and Deployment project in Visual Studio 2005.

The benefit of creating Interop UserControl libraries is that you can deploy them using what's called RegFree-COM. This means that if you're deploying your VB6 applications on XP or higher OSs then you don't have to manually register the assemblies as COM components. Instead the operating system will "register" them for you on-the-fly at runtime. The COM component registry keys aren't physically created in the system's registry, keeping the system clean and allowing you to deploy COM components in a side-by-side fashion. (On your development machine the UserControls are still physically registered when you build the assemblies so that you can develop in Visual Basic 6.) This may be the way to go if you have a current setup program that you use for your VB6 application that was written with the Setup and Deployment Wizard.

Using RegFree-COM also means that you can take advantage of ClickOnce Deployment (since ClickOnce will not register COM components). The benefit of using ClickOnce deployment with your VB6 applications is that you can take advantage of a modern deployment architecture which includes automatic updates and deploying from a central web server.

What the toolkit does to enable RegFree-COM is that it gets you started with a private assembly manifest for the UserControls. When you select the Interop User Control Library template when creating a new project in Visual Studio, a InteropUserControl.manifest file is created for you along with the rest of the files. Inside this private assembly manifest is XML describing the controls inside your library. By default, the name of the UserControl that is created when you first create your library is InteropUserControl with a generated class id. This information is described in the <clrClass> node. 

 

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!-- You don't need to worry about anything in this file unless you're
     using registration-free COM.
     There should be an appropriate <clrclass> section for every InteropUserControl
     defined in the project -->
<assembly xmlns="urn:schemas-microsoft-com:asm.v1"
  manifestVersion="1.0">
<assemblyIdentity
            type="win32"
            name="InteropUserControlLibrary1"
            version="1.0.0.0" />
<clrClass
clsid="{7395b9d9-da47-4262-80e7-b4eed8768d09}"
progid="InteropUserControlLibrary1.InteropUserControl"
threadingModel="Both"
name="InteropUserControlLibrary1.InteropUserControl" >
</clrClass>

</assembly>

When you change the name of InteropUserControl or the assembly name you need to also update the contents of this file as well. If you add more UserControls to your library you'll need to create a new <clrClass> node with that control's information. You can find the class ID by looking in the Interop UserControl's VB6 Interop Code Region and then in the COM Registration Region:

<ComClass(InteropUserControl.ClassId, InteropUserControl.InterfaceId, InteropUserControl.EventsId)> _

Public Class InteropUserControl

#Region "VB6 Interop Code"

#If COM_INTEROP_ENABLED Then

#Region "COM Registration"

    ' 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 = "7395b9d9-da47-4262-80e7-b4eed8768d09"

    Public Const InterfaceId As String = "739b2e94-6bdf-4417-beff-25a457e6df8f"

    Public Const EventsId As String = "65029223-ea4e-4cb0-9462-27edbbf985fa"

    'These routines perform the additional COM registration needed by ActiveX controls

    <EditorBrowsable(EditorBrowsableState.Never)> _

    <ComRegisterFunction()> _

    Private Shared Sub Register(ByVal t As Type)

        ComRegistration.RegisterControl(t)

    End Sub

    <EditorBrowsable(EditorBrowsableState.Never)> _

    <ComUnregisterFunction()> _

    Private Shared Sub Unregister(ByVal t As Type)

        ComRegistration.UnregisterControl(t)

    End Sub

#End Region

As an example, I created an Interop UserControl library called ARegFreeComLibrary. In this library I created two UserControls, one called CustomerDetails and one called OrderDetails so my manifest looks like this:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!-- You don't need to worry about anything in this file unless you're
     using registration-free COM.
     There should be an appropriate <clrclass> section for every InteropUserControl
     defined in the project -->
<assembly xmlns="urn:schemas-microsoft-com:asm.v1"
  manifestVersion="1.0">
<assemblyIdentity
            type="win32"
            name="ARegFreeComLibrary"
            version="1.0.0.0" />
<clrClass
            clsid="{7395b9d9-da47-4262-80e7-b4eed8768d09}"
            progid="ARegFreeComLibrary.CustomerDetails"
            threadingModel="Both"
            name="ARegFreeComLibrary.CustomerDetails" >
</clrClass>
  <clrClass
            clsid="{bb58ec27-8668-49e0-90cf-3542075654a9}"
            progid="ARegFreeComLibrary.OrderDetails"
            threadingModel="Both"
            name="ARegFreeComLibrary.OrderDetails" >
  </clrClass>
</assembly>

The other manifest file you need to have for RegFree-COM to work is called a client manifest file and this needs to be manually created and then placed in the application's directory. This would be where your VB6 application is also deployed. The contents of this manifest include the name and version of the VB6 application and then the Interop UserControl assembly information:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
    <assemblyIdentity type="win32" name="MyVB6App" version="1.0.0.0" processorArchitecture="x86" />
        <dependency>
            <dependentAssembly>
                 <assemblyIdentity type="win32" name="ARegFreeComLibrary" version="1.0.0.0" />
            </dependentAssembly>
        </dependency>
</assembly>

Place this into a file named after your VB6 application with an extension of .manifest, in my case, MyVB6App.exe.manifest, and pop it into the same folder as the exe. It's important that you name the client manifest file the same as your VB6 application + exe.manifest extension otherwise RegFree-COM will not work. To manage the file easier, from the Project menu in Visual Studio I selected Add New Item and selected Text File to create the manifest. In the properties window I then set the "Build Action" to Content and "Copy to Output Directory" to Copy Always. I find it easier to keep track of it this way.  

Next thing to do is copy your VB6 app into the release folder under \bin\Release\ and any supporting files. In my example I am using an Access mdb, but I've added this file to the library I created as a local data file so it'll get built into the Release folder as well. Note, when working with local data files you'll need to also set the Copy to Output Directory appropriately like I explain in this post. Now what the client is going to need in order to run all this is:

1) The .NET Framework 2.0
2) The VB runtime (comes with XP and up OS's) and any OCX's or other referenced components/data you use in your VB6 application
4) Your Interop UserControl assembly and any .config files and the client .exe.manifest
3) The Microsoft.InteropFormTools assembly

When creating Interop UserControls you can remove the reference to the Microsoft.InteropFormTools assembly if you aren't using it -- just remove the reference and then comment out the code at the top of the ActiveXControlHelpers.vb file. However, if you are using it then it's easy to get ClickOnce to install this as a prerequisite (just like the Framework 2.0) because the toolkit now comes with a redistributable package. (You'll see this in a minute.)

You can also add your VB6 application to the project which, in my example, includes the MyVB6App.exe and the Msdatgrd.ocx (the VB6 DataGrid I'm using). Adding them as content files and marking them to always copy to output directory will put them into the \bin\Release folder every time you build the UserControl project. You can also specify the class library project location when you make your VB6 EXE, that way all the files you need to deploy come out in one place, the \bin\Release. If you're deploying this to a machine that already has the VB6 Runtime and the .NET 2.0 Framework then you can just xcopy deploy the Release to the client machine and run.

Using ClickOnce Deployment is optional but for this example we'll set it up this way. Since you can't use ClickOnce to deploy a class library project, instead of adding the VB6 application files to the class library project, we'll add them to a new Windows Forms application instead. So I just added a simple Windows Application to my solution called InteropAppLauncher. Once you have this project created you can then add a reference to your Interop UserControl Assembly and select "Add Existing Item" on the VB6 application files as well as the client manifest file (MyVB6App.exe.manifest). In my case, since I'm using an Access.mdb file I moved this file into the launcher application as well so it can be deployed.

Next thing to do is to write a couple lines of code in the launcher's form so that it will launch your VB6 application right away and close.

Public Class Form1

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

        Process.Start("MyVB6App.exe")

        Me.Close()

    End Sub

End Class

Now we're ready to publish with ClickOnce. Select My Project and go to the Publish Tab and enter the deployment location. I just set up a local file share, but you can easily publish to a web server as well. Select the Prerequisites button and you'll see the redistributable packages available. You'll definitely need .NET Framework 2.0 but you can decide whether you need the Interop Forms Toolkit 2.0 package depending on if you left the reference to Microsoft.InteropFormTools assembly. If you did, then you'll need to include this package.

Once you've set all that up you can click the Publish Now button or select Publish from the build menu. Now you have a ClickOnce setup that will launch your VB6 application which uses RegFree-COM in order to create a friction free deployment for your Interop User Controls. I've included the sample I built for your reference. To try out RegFree-COM, just compile the solution and then run the InteropAppLauncher.exe in the \ARegFreeComLibrary\InteropAppLauncher\bin\Release\ folder. For more info on setting up ClickOnce to deploy Visual Basic 6 applications, check out Scott's article on MSDN and this LabCast to try it out yourself right now. By the way, you can use ClickOnce for deploying Visual FoxPro applications as well. 

Anyone developing Visual Basic 6 (or Visual FoxPro) applications should consider taking advantage of the Interop Forms Toolkit 2.0, ClickOnce Deployment, and RegFree-COM to bring .NET's modern capabilities to your users in an easy-to-manage way.

In the next interop post I'll talk about setting up debugging across Visual Studio and VB6. (UPDATE: Here's how to debug.)

ARegFreeComLibrary.zip