从 ShowDialog 表单引发的事件不会通过 com interop 一直引发到调用 vb6 应用程序吗?

Events raised from a ShowDialog Form aren't raised all the way to a calling vb6 app via com interop?

我们有一个遗留的 VB6 应用程序,我们长期以来一直在其中调用 .net 程序集,包括从 .net 程序集显示 WinForms。但现在我还需要从 .net 程序集、从 WinForm 引发事件,然后返回到 VB6 应用程序。

当使用 .Show 显示表单时,这有效(触发 VB6 事件)。但是当表单显示 .ShowDialog 时,该事件不会在 VB6 应用程序中触发。而且,当然,我需要以模态方式显示表格,所以这就是使用 .ShowDialog 的原因。

代码:

创建 .net class 库,启用 com 互操作。当我在我的机器上创建它时,它被命名为 ClassLibrary2。

Option Strict On
Option Explicit On

<ComClass(Class1.ClassId, Class1.InterfaceId, Class1.EventsId)>
Public Class Class1

#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 = "3E245773-5A31-4B09-A26B-19D2E593395E"
    Public Const InterfaceId As String = "5A184A72-AE12-4564-83FB-15EEAC8C9A13"
    Public Const EventsId As String = "75C80E42-6B66-4B43-A1FA-BD62C95D117E"
#End Region

    Public Sub New()
        MyBase.New
    End Sub

    Public Event MyEvent(sParm As String)

    Private WithEvents ofrm As Form1

    Public Sub MySub()
        RaiseEvent MyEvent("MySub Entry")
        ofrm = New Form1
        'ofrm.Show()            ' With .Show, all events are raised to calling app
        ofrm.ShowDialog()       ' With .ShowDialog, events from the form are raised to this class, but then subsequently aren't raised to the calling app
        RaiseEvent MyEvent("MySub Exit")
    End Sub

    Private Sub ofrm_MyFormEvent(sParm As String) Handles ofrm.MyFormEvent
        RaiseEvent MyEvent(sParm)
    End Sub
End Class

窗体添加到程序集中,然后一个按钮添加到窗体。

Option Strict On
Option Explicit On

Public Class Form1

    Public Event MyFormEvent(sParm As String)

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        RaiseEvent MyFormEvent("Form Closing")
        Me.Close()
    End Sub

    Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown
        RaiseEvent MyFormEvent("Form Shown")
    End Sub
End Class

VB6 可执行应用程序,窗体上有一个按钮,还添加了对 ClassLibrary2 程序集的引用。

Private WithEvents ox As ClassLibrary2.Class1

Private Sub Command1_Click()
  Set ox = New ClassLibrary2.Class1
  Call ox.MySub
End Sub

Private Sub ox_MyEvent(ByVal sParm As String)
  Debug.Print sParm
End Sub

当 运行 处理所有这些时,从 MySub 引发的事件将触发 VB6 ox_MyEvent 事件处理程序。当 Form1 使用 .Show 显示时,从表单引发的事件会触发 ofrm_MyFormEvent 处理程序,这会进一步引发事件,并且 VB6 ox_MyEvent 事件处理程序会触发:

MySub Entry
MySub Exit
Form Shown
Form Closing

但是当 Form1 以 .ShowDialog 显示时,从表单引发的事件会触发 ofrm_MyFormEvent 处理程序,但从那里引发的事件永远不会触发 VB6 ox_MyEvent 事件处理程序:

MySub Entry
MySub Exit

这里发生了什么?某种 VB6 UI 线程阻塞?但是,当从 MySub 引发的事件可以通过时,为什么 ShowDialog 会这样做?

使用 VS2015,框架 4.5.2

更新

我已经 运行 另一个测试,使用 .net Winform 应用程序 (exe) 作为调用应用程序,而不是上面的 VB6 代码。尝试将 ClassLibrary2 程序集作为 .net 程序集以及 COM 对象调用。在这两种情况下,事件都按预期触发了主应用程序:

MySub Entry
Form Shown
Form Closing
MySub Exit

所以这不是 COM 问题(或者至少不仅仅是 COM),肯定涉及 VB6 问题?

模态对话框有自己的内部消息泵。也许是因为内部消息泵是 .NET 控制的而不是 VB6 控制的,所以存在不兼容性?比如,VB6 消息循环满足了 VB6 需要而 .NET 消息泵没有做的事情?

无论如何,我个人放弃了 COM 互操作场景中的事件,转而使用显式回调接口。 这样做的好处是根本不涉及消息泵,您可以绕过与此相关的任何问题。

所以不是 Public Event MyEvent(sParm As String) 创建接口调用 IClass1EventListenerIClass1CallbackHandler 并给它一个方法 Sub OnMyFormEvent(sParm As String)

然后在您的 VB6 表单中使用 Implements IClass1EventListener.

您的代码看起来更像这样:

Private ox As ClassLibrary2.Class1

Private Sub Command1_Click()
  Set ox = New ClassLibrary2.Class1
  Call ox.MySub(Me)
End Sub

Private Sub IClass1EventListener_OnMyEvent(ByVal sParm As String)
  Debug.Print sParm
End Sub

您应该注意让 .NET 端在完成 ShowDialog 后明确清除回调引用,或者将方法签名更改为您的 VB6 代码可以管理它的方法签名,如下所示:

Private Sub Command1_Click()
  Set ox = New ClassLibrary2.Class1
  Set ox.Listener = Me
  Call ox.MySub()
  Set ox.Listener = Nothing
End Sub