1
votes

I have a .NET interface

<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

in an exported type library.

The VB6 code references this tlb and implements this interface.

The VB6 code creates an instance of it's implementation and passes it to .NET.

.NET calls OnEvent.

VB6 picks it up the event fine...but the sender and e argument values are strings, not objects when it gets there... The string values are the full names of the types...

The VB6 code:

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

The code that fires the event is fairly trivial. I have a COM class with a dictionary that registers handlers and fires events.

<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

The .NET code looks like

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 })

If I wire up the same code using a .NET class that implements IEventHandler, the arguments come through without a problem.

UPDATE: If I change my ByRef parameters for OnEvent to ByVal, it makes no difference. I'm sure the two types I'm trying to pass are from an assembly marked as ComVisible.

What's going wrong here?

1
You didn't post the code that calls OnEvent and passes the values for the arguments. Rather relevant. You'd also have to ensure that the classes you expose through the arguments are ComVisible. - Hans Passant
Also note that COM interop can expose "real" COM events that VB can subscribe to. - Deanna
Yikes, did you really intend on sending those object args ByRef? - tcarvin
@Hans: Yes they're all ComVisible. Will update with calling code. - Jeff
@Deanna: I know but I'm not a fan. - Jeff

1 Answers

1
votes

OK, since we've confirmed that the parameters are coming through as (COM) objects and not Strings, the last mystery to solve is why do you get an Error 13, Type mismatch.

I would explore this issue on two fronts: First, use TypeName() to get at the object VB6 thinks its got (e.g. TypeName(e.Person)). Then, think about whether VB6 really has the means to get at the members of that object - a good place to start is with the Object Browser in the VB6 IDE. If the object class shows up in that yet exposes no members (even with Show Hidden Members enabled), then this class probably isn't being properly mapped out as a COM object.

==== Previous ====

For the giggles, when you Stop or break-point into Sub IEventHandler_OnEvent(), go into the Immediate window of the VB6 IDE and see if these are legal:

?sender.ToString
?e.ToString

Also, give up on using ByRef and go with ByVal; unless you're really trying to change the value of a reference versus the state of an instance, you don't need ByRef.

My suspicion is that you are really getting the objects; the apparent string you see is the result of the ToString() member, which is somehow made as the COM default property.

One way to check the above suspicion is to try these in the Immediate window of the VB6 IDE (when execution is stopped within IEventHandler_OnEvent()):

?typename(sender)
?typename(e)

Typename should show String if either of these is really a string.

==== Previous ====

Jeff, please let me know if these VB6 and .NET projects are a valid case study of your issue.

In .NET, I created a DLL project having one interface and one class, both with COM exposure:

==== .NET, File 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, File 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

In VB6, I created a standard EXE project with references to the .NET-based COM library via the TLB file, having one form (containing one button) and one class like so:

==== VB6, File 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, File Class1.cls

Implements ComFun.IComFun

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

  Stop

End Sub

I made the above projects and compiled them, and then ran the VB6 EXE (in the IDE) and clicked the button. When I got to the Stop statement in IComFun_OnFoo, I was getting the actual objects and not strings - in other words, it worked the way you needed it to work. I also looked at the call stack (in VB6 IDE) to confirm that non-Basic code was being called.

So, I didn't really solve your problem, but I'm hoping to better understand the situation.