How to: Support COM Interop by Displaying Windows Forms on a Shared Thread
You can resolve Component Object Model (COM) interoperability problems by displaying your form on a .NET Framework message loop, which is created by using the System.Windows.Forms.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:
-
Use the System.Windows.Forms.Form.ShowDialog method to display the Windows Form. For more information, see How to: Support COM Interop by Displaying a Windows Form with the ShowDialog Method.
-
Display each Windows Form on a new thread. For more information, see How to: Support COM Interop by Displaying Each Windows Form on Its Own Thread.
-
Create a shared message loop on a new thread in the .NET Framework component.
The following code example demonstrates how to display Windows Forms on a new thread with a shared message loop.
There is extensive support for this feature in Visual Studio.
Example
Displaying Windows Forms on a shared thread is similar to the approach shown in How to: Support COM Interop by Displaying Each Windows Form on Its Own Thread. However, instead of displaying each form on its own thread by using its own message loop, you create a shared message loop that runs on only one new thread in the .NET Framework component.
This approach more accurately represents the behavior of a standard Windows Forms application. It also makes it easier for you to share resources between multiple forms, because all the forms run on the same thread. The solution in How to: Support COM Interop by Displaying Each Windows Form on Its Own Thread creates a new thread for each form. That solution requires additional thread synchronization code to share resources between different forms.
Because displaying forms on a shared thread is more similar to the behavior of a Windows Forms application, you will see that with .NET Framework Windows Forms the client application will close when the .NET Framework message loop stops. This behavior occurs when the user closes the form that is designated as the main form for the ApplicationContext. The ApplicationContext is used to start the message loop.
In the following code examples, the main form of the ApplicationContext is set to the first form that the client application opens. Therefore, when the user closes that form instance, the .NET Framework message loop exits, and all other Windows Forms will close.
Imports System.Windows.Forms Imports System.Runtime.InteropServices <ComClass(COMForm.ClassId, COMForm.InterfaceId, COMForm.EventsId)> _ Public Class COMForm #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 = "9322c6dd-2738-428b-ba89-414ce2ea1941" Public Const InterfaceId As String = "210f5b8e-296a-4e26-bd7b-cd2cffa43389" Public Const EventsId As String = "f25c0ebb-2a2e-42b5-bf20-4bb84989a7da" #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 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 End Class
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
Imports System.Windows.Forms Public Class Form1 Inherits System.Windows.Forms.Form Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click MessageBox.Show("Clicked button") End Sub 'Form overrides dispose to clean up the component list. <System.Diagnostics.DebuggerNonUserCode()> _ Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean) If disposing AndAlso components IsNot Nothing Then components.Dispose() End If MyBase.Dispose(disposing) End Sub 'Required by the Windows Form Designer Private components As System.ComponentModel.IContainer 'NOTE: The following procedure is required by the Windows Form Designer 'It can be modified using the Windows Form Designer. 'Do not modify it using the code editor. <System.Diagnostics.DebuggerStepThrough()> _ Private Sub InitializeComponent() Me.TextBox1 = New System.Windows.Forms.TextBox Me.TextBox2 = New System.Windows.Forms.TextBox Me.TextBox3 = New System.Windows.Forms.TextBox Me.Button1 = New System.Windows.Forms.Button Me.SuspendLayout() ' 'TextBox1 ' Me.TextBox1.Location = New System.Drawing.Point(12, 12) Me.TextBox1.Name = "TextBox1" Me.TextBox1.Size = New System.Drawing.Size(100, 20) Me.TextBox1.TabIndex = 0 ' 'TextBox2 ' Me.TextBox2.Location = New System.Drawing.Point(12, 38) Me.TextBox2.Name = "TextBox2" Me.TextBox2.Size = New System.Drawing.Size(100, 20) Me.TextBox2.TabIndex = 1 ' 'TextBox3 ' Me.TextBox3.Location = New System.Drawing.Point(12, 64) Me.TextBox3.Name = "TextBox3" Me.TextBox3.Size = New System.Drawing.Size(100, 20) Me.TextBox3.TabIndex = 2 ' 'Button1 ' Me.Button1.Location = New System.Drawing.Point(12, 90) Me.Button1.Name = "Button1" Me.Button1.Size = New System.Drawing.Size(75, 23) Me.Button1.TabIndex = 3 Me.Button1.Text = "Button1" Me.Button1.UseVisualStyleBackColor = True ' 'Form1 ' Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!) Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font Me.ClientSize = New System.Drawing.Size(130, 138) Me.Controls.Add(Me.Button1) Me.Controls.Add(Me.TextBox3) Me.Controls.Add(Me.TextBox2) Me.Controls.Add(Me.TextBox1) Me.Name = "Form1" Me.Text = "Form1" Me.ResumeLayout(False) Me.PerformLayout() End Sub Friend WithEvents TextBox1 As System.Windows.Forms.TextBox Friend WithEvents TextBox2 As System.Windows.Forms.TextBox Friend WithEvents TextBox3 As System.Windows.Forms.TextBox Friend WithEvents Button1 As System.Windows.Forms.Button End Class
Compiling the Code
-
Compile the COMForm, Form1, and FormManager types into an assembly called COMWinform.dll. Register the assembly for COM interop by using one of the methods described in Packaging an Assembly for COM. You can now use the assembly and its corresponding type library (.tlb) file in unmanaged applications. For example, you can use the type library as a reference in a Visual Basic 6.0 executable project.
See Also
Tasks
How to: Support COM Interop by Displaying a Windows Form with the ShowDialog MethodHow to: Support COM Interop by Displaying Each Windows Form on Its Own Thread
Concepts
Exposing .NET Framework Components to COMPackaging an Assembly for COM
Registering Assemblies with COM
Windows Forms and Unmanaged Applications Overview