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

Mar*_*rkL 5 vb.net vb6 com-interop

我们有一个旧版VB6应用程序,在其中我们已经调用了很长时间了,包括显示.net程序集中的WinForms。但是现在我还需要从.net程序集,从WinForm到VB6应用程序引发事件。

当使用显示表单时,此方法有效(触发VB6事件).Show。但是,当表单显示为时.ShowDialog,该事件不会在VB6应用中触发。而且,当然,我需要模态显示表单,这就是为什么.ShowDialog要使用它。

码:

创建一个.net类库,启用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
Run Code Online (Sandbox Code Playgroud)

将表单添加到装配体,然后将一个按钮添加到表单。

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

运行所有这些命令时,引发的事件将MySub触发VB6 ox_MyEvent事件处理程序。当Form1与一起显示时.Show,从表单ofrm_MyFormEvent引发的事件将触发处理程序,从而进一步引发事件,而VB6 ox_MyEvent事件处理程序将触发:

MySub Entry
MySub Exit
Form Shown
Form Closing
Run Code Online (Sandbox Code Playgroud)

但是,当Form1与一起显示时.ShowDialog,从窗体引发的事件将触发ofrm_MyFormEvent处理程序,但从那里引发的事件将永远不会触发VB6 ox_MyEvent事件处理程序:

MySub Entry
MySub Exit
Run Code Online (Sandbox Code Playgroud)

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

使用VS2015,Framework 4.5.2

更新资料

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

MySub Entry
Form Shown
Form Closing
MySub Exit
Run Code Online (Sandbox Code Playgroud)

因此,这不是COM问题(或者至少不是COM问题),肯定与VB6有关吗?

tca*_*vin 0

模态对话框有自己的内部消息泵。也许是因为内部消息泵是.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
Run Code Online (Sandbox Code Playgroud)

您应该注意,要么让 .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
Run Code Online (Sandbox Code Playgroud)