【问题标题】:VB6 .NET Interop Arguments Passed As StringsVB6 .NET 互操作参数作为字符串传递
【发布时间】:2011-12-21 17:59:37
【问题描述】:

我有一个 .NET 接口

<System.Runtime.InteropServices.GuidAttribute("0896D946-8A8B-4E7D-9D0D-BB29A52B5D08"), _
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)> _
Public Interface IEventHandler
    Sub OnEvent(ByRef sender As Object, ByRef e As Object)
End Interface

在导出的类型库中。

VB6 代码引用了这个 tlb 并实现了这个接口。

VB6 代码创建其实现的实例并将其传递给 .NET。

.NET 调用 OnEvent。

VB6 可以很好地接收事件...但是 sender 和 e 参数值是字符串,而不是当它到达那里时的对象...字符串值是类型的全名...

VB6 代码:

Implements Interop.IEventHandler

Private Sub IEventHandler_OnEvent(ByRef sender As Variant, ByRef e As Variant)
    Dim id
    id = e.Person.Id

    ' The weird thing here:
    ' e = "XYZ.Tasks.PersonTaskEventArgs"
    ' sender = "XYZ.Tasks.PersonUIManager"
    ' The values of the arguments are the NAMEs of the actual object values' types...
End Sub

触发事件的代码相当简单。我有一个带有字典的 COM 类,它注册处理程序并触发事件。

<ComClass(ComRegistrar.ClassId, ComRegistrar.InterfaceId, ComRegistrar.EventsId>
Public Class ComRegistrar

   Private Shared ReadOnly _eventHandlers As New Dictionary(Of String, List(Of IEventHandler))


   ' This is called by .NET code to fire events to VB6
   Public Shared Sub FireEvent(ByVal eventName As String, ByVal sender As Object, ByVal e As Object)
        For Each eventHandler In _eventHandlers(eventName)
                eventHandler.OnEvent(sender, e)
        Next
   End Sub

   Public Sub RegisterHandler(ByVal eventName As String, ByVal handler As IEventHandler)
        Dim handlers as List(Of IEventHandler)
        If Not _eventHandlers.TryGetValue(eventName, handlers)
             handlers = New List(Of IEventHandler)
             _eventHandlers(eventName) = handlers
        End If
        handlers.Add(handler)
   End Sub

End Class

.NET 代码看起来像

Public Class PersonEventArgs
     Inherits System.EventArgs


' Some properties
End Class

Public Class MyControl
     Inherits UserControl

' Stuff
End Class


ComRegistrar.FireEvent("PersonSelected", Me, New PersonEventArgs With { Some stuff })

如果我使用实现 IEventHandler 的 .NET 类连接相同的代码,则参数可以毫无问题地通过。

更新:如果我将 OnEvent 的 ByRef 参数更改为 ByVal,则没有区别。我确定我要传递的两种类型来自标记为 ComVisible 的程序集。

这里出了什么问题?

【问题讨论】:

  • 您没有发布调用 OnEvent 并传递参数值的代码。比较相关。您还必须确保通过参数公开的类是 ComVisible。
  • 另请注意,COM 互操作可以公开 VB 可以订阅的“真实”COM 事件。
  • 哎呀,你真的打算发送那些对象参数 ByRef 吗?
  • @Hans:是的,它们都是 ComVisible。将使用调用代码进行更新。
  • @Deanna:我知道,但我不是粉丝。

标签: .net vb6 interop com-interop


【解决方案1】:

好的,既然我们已经确认参数是作为 (COM) 对象而不是字符串传入的,最后要解决的谜题是为什么会出现错误 13,类型不匹配。

我将在两个方面探讨这个问题:首先,使用 TypeName() 来获取 VB6 认为它得到的对象(例如 TypeName(e.Person))。然后,考虑一下 VB6 是否真的有办法获取该对象的成员 - 一个很好的起点是使用 VB6 IDE 中的对象浏览器。如果对象类出现在其中但没有暴露任何成员(即使启用了显示隐藏成员),那么这个类可能没有被正确地映射为 COM 对象。

==== 上一页 ====

对于傻笑,当你停止或断点到 Sub IEventHandler_OnEvent() 时,进入 VB6 IDE 的即时窗口,看看这些是否合法:

?sender.ToString
?e.ToString

另外,放弃使用 ByRef 并使用 ByVal;除非你真的想改变引用的值而不是实例的状态,否则你不需要 ByRef。

我的怀疑是你真的得到了这些对象;您看到的明显字符串是 ToString() 成员的结果,它以某种方式被设置为 COM 默认属性。

检查上述怀疑的一种方法是在 VB6 IDE 的即时窗口中尝试这些(在 IEventHandler_OnEvent() 中停止执行时):

?typename(sender)
?typename(e)

如果其中任何一个确实是字符串,则类型名称应显示字符串。

==== 上一页 ====

Jeff,如果这些 VB6 和 .NET 项目是您问题的有效案例研究,请告诉我。

在 .NET 中,我创建了一个 DLL 项目,它具有一个接口和一个类,都具有 COM 暴露:

==== .NET,文件 IComFun.vb

Imports System.Runtime.InteropServices

<GuidAttribute("b3f1ab4f-dc99-4990-ade1-8a4833d8bcab"), _
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)> _
Public Interface IComFun
    Sub OnFoo(ByVal sender As Object, ByVal e As Object)
End Interface

==== .NET,文件 ComFun.vb

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

#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 = "257a4ed2-1f27-4d8e-bcf2-f90828ae649b"
    Public Const InterfaceId As String = "4431a515-3c01-4fff-b281-20ec8ad0c0b6"
    Public Const EventsId As String = "9b44258f-142e-4dd0-bc22-6d24d1f58657"
#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


    Public Sub Ping(ByVal sender As Object, ByVal e As Object)
        DirectCast(sender, IComFun).OnFoo(sender, e)
    End Sub

End Class

在 VB6 中,我创建了一个标准的 EXE 项目,通过 TLB 文件引用基于 .NET 的 COM 库,具有一个表单(包含一个按钮)和一个类,如下所示:

==== VB6,文件 Form1.frm

Dim x As Class1
Dim y As Class1
Dim z As ComFun.ComFun

Private Sub cmdPing_Click()

  If x Is Nothing Then
    Set x = New Class1
  End If

  If y Is Nothing Then
    Set y = New Class1
  End If

  If z Is Nothing Then
    Set z = New ComFun.ComFun
  End If

  z.Ping x, y

End Sub

==== VB6,文件 Class1.cls

Implements ComFun.IComFun

Private Sub IComFun_OnFoo(ByVal sender As Variant, ByVal e As Variant)

  Stop

End Sub

我制作了上述项目并编译它们,然后运行 ​​VB6 EXE(在 IDE 中)并单击按钮。当我到达 IComFun_OnFoo 中的 Stop 语句时,我得到的是实际的对象而不是字符串 - 换句话说,它按照您需要的方式工作。我还查看了调用堆栈(在 VB6 IDE 中)以确认正在调用非基本代码。

所以,我并没有真正解决你的问题,但我希望能更好地了解情况。

【讨论】:

  • 谢谢!...让我们看看...这里有区别:我在 tlb 导出库中有两个不同的 COM 类。一个是 IEventHandler 接口(如问题所示)。没有.NET 实现。另一个是 ComRegistrar。 ComRegistrar 有一个公共子 RegisterHandler(事件名称为字符串,处理程序为 IEventHandler)。 VB6 代码创建一个新的 ComRegistrar 实例并调用 RegisterHandler,传入它的 IEventHandler 实现的一个实例。稍后(例如当有人单击 .NET 表单上的按钮时),具有注册名称的事件被触发,并且 FireEvent 方法
  • 从问题被调用。在我的例子中,sender 和 e 是一个 UserControl 和一个派生的 EventArgs 类(称为 PersonEventArgs),位于一个单独的程序集中(恰好是 COM Visible)。我正在更新问题以包含更多相关代码。
  • 它们绝对是字符串。在 VB6 即时窗口中,我可以执行 Mid(e,1,1) 它给了我类型名称的第一个字母....
  • 此外,从 VB6 调用 e.GetType() 或 e.ToString() 会引发互操作错误(不支持)。
  • 另外,从 ByRef 切换到 ByVal 也没有效果。如果我在调用 OnFoo (On​​Event) 之前立即放置一条日志语句以从 .NET 中打印出 sender 和 e 的类型,它会验证对象的类型是否正确(不是字符串)
猜你喜欢
  • 1970-01-01
  • 2018-07-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多