Compartir a través de


Cómo: Admitir la interoperabilidad COM al mostrar formularios Windows Forms en un subproceso compartido

Actualización: noviembre 2007

Puede solucionar problemas de interoperabilidad de Modelo de objetos componentes (COM) mostrando el formulario en un bucle de mensajes de .NET Framework, que se crea con el método Application.Run.

Para que un formulario Windows Forms funcione correctamente desde una aplicación cliente COM, debe ejecutarlo en un bucle de mensajes de formularios Windows Forms. Para ello, utilice uno de los enfoques siguientes:

En el ejemplo de código siguiente se muestra cómo visualizar formularios Windows Forms en un nuevo subproceso con un bucle de mensajes compartido.

Visual Studio ofrece una amplia compatibilidad para esta característica.

Ejemplo

Mostrar formularios Windows Forms en un subproceso compartido es similar al enfoque mostrado en Cómo: Admitir la interoperabilidad COM al mostrar cada formulario Windows Forms en su propio subproceso. No obstante, en lugar de mostrar cada formulario en su propio subproceso utilizando su propio bucle de mensajes, puede crear un bucle de mensajes compartido que se ejecuta únicamente en un nuevo subproceso del componente .NET Framework.

Este enfoque representa con más precisión el comportamiento de una aplicación de Windows Forms estándar. También hace que sea más sencillo compartir recursos entre varios formularios, porque todos los formularios se ejecutan en el mismo subproceso. La solución en Cómo: Admitir la interoperabilidad COM al mostrar cada formulario Windows Forms en su propio subproceso crea un nuevo subproceso para cada formulario. Esa solución requiere código de sincronización de subprocesos adicional para compartir recursos entre formularios distintos.

Dado que mostrar formularios en un subproceso compartido es más similar al comportamiento de una aplicación de Windows Forms, verá que con formularios Windows Forms de .NET Framework se cerrará la aplicación cliente cuando se detenga el bucle de mensajes de .NET Framework. Este comportamiento aparece cuando el usuario cierra el formulario que está designado como el principal de la clase ApplicationContext. Se utiliza ApplicationContext para iniciar el bucle de mensajes.

En los ejemplos de código siguientes, el formulario principal de la clase ApplicationContext se establece en el primer formulario que la aplicación cliente abre. Por consiguiente, cuando el usuario cierra esa instancia de formulario, el programa sale del bucle de mensajes de .NET Framework y se cerrarán todos los demás formularios Windows Forms.

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

Compilar el código

  • Compile los tipos: COMForm, Form1 y FormManager en un ensamblado llamado COMWinform.dll. Registre el ensamblado para la interoperabilidad COM utilizando uno de los métodos descritos en Empaquetar un ensamblado para COM. Ahora puede utilizar el ensamblado y el archivo de biblioteca de tipos correspondiente (.tlb) en aplicaciones no administradas. Por ejemplo, puede utilizar la biblioteca de tipos como una referencia en un proyecto ejecutable de Visual Basic 6.0.

Vea también

Tareas

Cómo: Admitir la interoperabilidad COM al mostrar un formulario Windows Forms con el método ShowDialog

Cómo: Admitir la interoperabilidad COM al mostrar cada formulario Windows Forms en su propio subproceso

Conceptos

Exponer componentes de .NET Framework en COM

Empaquetar un ensamblado para COM

Registrar ensamblados con COM

Información general sobre formularios Windows Forms y aplicaciones no administradas