如何:通过在共享线程上显示 Windows 窗体来支持 COM Interop

更新:2007 年 11 月

通过在 .NET Framework 消息循环(该循环是使用 Application.Run 方法创建的)上显示窗体,可以解决组件对象模型 (COM) 互操作性问题。

若要使 Windows 窗体在 COM 客户端应用程序中正确工作,必须在 Windows 窗体消息循环上运行该窗体。若要实现这一点,请使用下列方法之一:

下面的代码示例演示如何用共享消息循环在新线程上显示 Windows 窗体。

Visual Studio 中对此功能提供了广泛的支持。

示例

在共享线程上显示 Windows 窗体与 如何:通过在每个 Windows 窗体各自的线程上显示该 Windows 窗体来支持 COM Interop 中显示的方法相似。但是,这并不是通过使用它自己的消息循环在自己的线程上显示每个窗体,而是在 .NET Framework 组件中创建只在一个新线程上运行的共享消息循环。

此方法可以更准确地表示标准 Windows 窗体应用程序的行为。由于所有窗体都在同一线程上运行,因此它还可以使您更轻松地在多个窗体之间共享资源。如何:通过在每个 Windows 窗体各自的线程上显示该 Windows 窗体来支持 COM Interop 中的解决方案为每个窗体创建新的线程。解决方案需要有其他线程同步代码,才能在不同窗体之间共享资源。

由于显示共享线程上的窗体更类似于 Windows 窗体应用程序的行为,因此,您将看到在使用 .NET Framework Windows 窗体的情况下,客户端应用程序将在 .NET Framework 消息循环停止时关闭。在用户关闭被指定为 ApplicationContext 的主窗体的窗体时,会发生此行为。ApplicationContext 用于启动消息循环。

在下面的示例中,ApplicationContext 的主窗体被设置为客户端应用程序所打开的第一个窗体。因此,用户关闭该窗体实例时,.NET Framework 消息循环将退出,并且所有其他 Windows 窗体都将关闭。

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

编译代码

  • 将 COMForm、Form1 和 FormManager 类型编译为称为 COMWinform.dll 的程序集。通过使用 将 COM 的程序集打包 中描述的一个方法,注册 COM 互操作 的程序集。现在,可以在非托管应用程序中使用程序集及其相应的类型库 (.tlb) 文件。例如,可以在 Visual Basic 6.0 可执行文件项目中将该类型库作为引用使用。

请参见

任务

如何:通过使用 ShowDialog 方法显示 Windows 窗体来支持 COM Interop

如何:通过在每个 Windows 窗体各自的线程上显示该 Windows 窗体来支持 COM Interop

概念

向 COM 公开 .NET Framework 组件

将 COM 的程序集打包

向 COM 注册程序集

Windows 窗体和非托管应用程序概述