Walkthrough: Supporting COM Interop by Displaying Each Windows Form on Its Own Thread

You can resolve COM interoperability problems by displaying your form on a .NET Framework message loop, which is created by using the Application.Run method.

To make a Windows Form work correctly from a COM client application, you must run it on a Windows Forms message loop. To do this, use one of the following approaches:

The following procedure demonstrates how to display a Windows Form on a separate thread.

To copy the code in this topic as a single listing, see How to: Support COM Interop by Displaying Each Windows Form on Its Own Thread.

Procedure

Put the form on a separate thread and call the Application.Run method to start a Windows Forms message pump on that thread. To use this approach, you must marshal any calls to the form from the unmanaged application by using the Invoke method.

This approach requires that each instance of a form runs on its own thread by using its own message loop. You cannot have more than one message loop running per thread. Therefore, you cannot change the client application's message loop. However, you can modify the .NET Framework component to start a new thread that uses its own message loop.

To create each instance of a Windows Form on a new thread

  1. Create a new Class Library project and name it COMWinform.

  2. Delete the default Class1.vb file.

  3. On the Project menu, click Add Class.

  4. Select the COM Class template.

  5. In the Name box, type COMForm.vb, and then click Add.

  6. Paste the following code statements at the top of the COMForm file, before the class definition.

    Imports System.Windows.Forms
    Imports System.Runtime.InteropServices
    
  7. In the COMForm class definition, paste the following code under the constructor definition.

    Private WithEvents frmManager As FormManager
    
    Public Sub ShowForm1()
        ' Call the StartForm method by using a new instance
        ' of the Form1 class.
        StartForm(New Form1)
    End Sub
    
    Private Sub StartForm(ByVal frm As Form)
    
        ' This procedure is used to show all forms
        ' that the client application requests. When the first form
        ' is displayed, this code will create a new message
        ' loop that runs on a new thread. The new form will
        ' be treated as the main form.
    
        ' Later forms will be shown on the same message loop.
        If IsNothing(frmManager) Then
            frmManager = New FormManager(frm)
        Else
            frmManager.ShowForm(frm)
        End If
    End Sub
    
    Private Sub frmManager_MessageLoopExit() _
    Handles frmManager.MessageLoopExit
    
        'Release the reference to the frmManager object.
        frmManager = Nothing
    
    End Sub
    
  8. On the Project menu, click Add Class and select the Class template.

  9. In the Name box, type FormManager.vb, and then click Add.

  10. Replace the contents of the FormManager file with the following code.

    Imports System.Runtime.InteropServices
    Imports System.Threading
    Imports System.Windows.Forms
    
    <ComVisible(False)> _
    Friend Class FormManager
        ' This class is used so that you can generically pass any
        ' form that you want to the delegate.
    
        Private WithEvents appContext As ApplicationContext
        Private Delegate Sub FormShowDelegate(ByVal form As Form)
        Event MessageLoopExit()
    
        Public Sub New(ByVal MainForm As Form)
            Dim t As Thread
            If IsNothing(appContext) Then
                appContext = New ApplicationContext(MainForm)
                t = New Thread(AddressOf StartMessageLoop)
                t.IsBackground = True
                t.SetApartmentState(ApartmentState.STA)
                t.Start()
            End If
        End Sub
    
        Private Sub StartMessageLoop()
            ' Call the Application.Run method to run the form on its own message loop.
            Application.Run(appContext)
        End Sub
    
        Public Sub ShowForm(ByVal form As Form)
    
            Dim formShow As FormShowDelegate
    
            ' Start the main form first. Otherwise, focus will stay on the 
            ' calling form.
            appContext.MainForm.Activate()
    
            ' Create a new instance of the FormShowDelegate method, and
            ' then invoke the delegate off the MainForm object.
            formShow = New FormShowDelegate( _
            AddressOf ShowFormOnMainForm_MessageLoop)
    
            appContext.MainForm.Invoke(formShow, New Object() {form})
        End Sub
    
        Private Sub ShowFormOnMainForm_MessageLoop(ByVal form As Form)
            form.Show()
        End Sub
    
        Private Sub ac_ThreadExit( _
        ByVal sender As Object, _
        ByVal e As System.EventArgs) _
        Handles appContext.ThreadExit
            appContext.MainForm.Dispose()
            appContext.MainForm = Nothing
            appContext.Dispose()
            appContext = Nothing
            RaiseEvent MessageLoopExit()
        End Sub
    End Class
    
  11. On the Project menu, click Add Windows Form, and then click Add.

  12. Add some TextBox controls and a Button control to the form.

  13. Double-click Button1 to add a Click event handler.

  14. Add the following code to the Click event handler.

    Private Sub Button1_Click( _
    ByVal sender As System.Object, _
    ByVal e As System.EventArgs) _
    Handles Button1.Click
        MessageBox.Show("Clicked button")
    End Sub
    
  15. Build the solution.

    This step also registers the project for COM interop on this computer.

To create an executable file that demonstrates COM interop

  1. Start Microsoft Visual Basic 6.0.

  2. Create a new standard EXE project.

  3. On the Project menu, click References.

  4. Add a reference to the COMWinform type library that was generated when you built the Visual Basic 2005 solution.

    -or-

    If you do not see it in the list, click Browse to locate the type library (.tlb) file manually.

  5. Add a button to the form.

  6. On the View menu, click Code, and then add the following code to the form module.

[Visual Basic]

Option Explicit

Private Sub Command1_Click()
    Dim frm As COMWinform.COMForm
    Set frm = New COMWinform.COMForm
    frm.ShowForm1
End Sub
  1. On the File menu, click Make EXE to compile the project.

  2. Run the compiled Visual Basic 6.0 executable file.

  3. Click the button to display the Windows Form from the class library that you created earlier.

  4. Set the focus on one of the TextBox controls on the Windows Form, and then press the TAB key to move between the controls.

    Notice that the TAB key moves focus from control to control. Also, notice that the button's Click event is raised when you press the ENTER key.

See Also

Tasks

How to: Support COM Interop by Displaying a Windows Form with the ShowDialog Method

Concepts

Exposing .NET Framework Components to COM

Packaging an Assembly for COM

Registering Assemblies with COM

Windows Forms and Unmanaged Applications Overview