Search code examples
vb.netcom-interop

VB.Net Com Interop - Sink COM Events to VBA Client


I'm programming a COM interop DLL in VB.NET and I need the events to be exposed to a VBA COM client. I just can't wrap my head around this one.

This is a COM wrapper for a .NET FTP library so don't get too confused by the fact that I'm raising a BytesTransferred event on a BytesTransferred event.

As I best recall, this code works but simply fails to show any events in the COM client when I use the object browser. It also fails to compile when I try to Dim my variable WithEvents:

Public Interface IFTP
    Event BytesTransferred(ByVal ByteCount As Long, ByVal RemoteFileName As String)
End Interface

'Here's the relevant part of my class:
Public Interface IFTP
    Event BytesTransferred(ByVal ByteCount As Long, ByVal RemoteFileName As String)
End Interface

Here's the relevant part of my class:

<ClassInterface(ClassInterfaceType.None)> _
    Public Class FTP : Implements IFTP

    Public Sub New()
        'This needed for com interop
    End Sub

    Public Event BytesTransferred(ByVal ByteCount As Long, ByVal RemoteFileName As String) Implements IFTP.BytesTransferred

    Private Sub fCon_BytesTransferred(ByVal sender As Object, ByVal e As EnterpriseDT.Net.Ftp.BytesTransferredEventArgs) Handles fCon.BytesTransferred
        RaiseEvent BytesTransferred(e.ByteCount, e.RemoteFile)
    End Sub

End Class

I've also tried something like this but I think I'm missing something here because it doesn't compile. I see an error that says I've failed to implement Sub BytesTransferred for interface IFTP:

Public Delegate Sub BytesTransferredDelegate(ByVal ByteCount As Long, ByVal RemoteFileName As String)

Public Interface IFTP
    <DispId(1)> _
    Sub BytesTransferred(ByVal ByteCount As Long, ByVal RemoteFileName As String)
End Interface

'Here's the relevant part of my class:

<ClassInterface(ClassInterfaceType.None)> _
    <ProgId("MyTestClass.FTP")> _
    <ComSourceInterfaces(GetType(IFTP))> _
Public Class FTP : Implements IFTP
    Public Event BytesTransferred As BytesTransferredDelegate

    Public Sub New()
        'This needed for com interop
    End Sub

    Private Sub fCon_BytesTransferred(ByVal sender As Object, ByVal e As EnterpriseDT.Net.Ftp.BytesTransferredEventArgs) Handles fCon.BytesTransferred
        RaiseEvent BytesTransferred(e.ByteCount, e.RemoteFile)
    End Sub

End Class

Solution

  • Solution:
    1) Declare Delegate Subs (with arguments if needed) at top of code module (inside your namespace if you are using one)
    2) Create a separate interface for your events, as shown below. Be sure to include the IDispatch declaration
    3) No need to put anything for your events in your primary interface.
    4) Use the ComSourceInterfaces declaration with your class declaration
    5) Inside your class declare your events using "As" to point to your Delegate Sub

    As mentioned by Hans Passant, be sure to use data types that are compatible with VB6/VBA. Long is not a compatible data type.

    Here's my code that now works:

    Public Delegate Sub BytesTransferredDelegate(ByVal ByteCount As Long, _
                        ByVal RemoteFileName As String)
    Public Delegate Sub OnUploaded()
    
    <InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)> _
    Public Interface IFTPEvents
        <DispId(1)> _
        Sub BytesTransferred(ByVal ByteCount As Double, _
                             ByVal RemoteFileName As String)
        <DispId(2)> _
        Sub Uploaded()
    End Interface
    
    Public Interface IFTP
        'Subs, Functions, Properties go here
        'No subs, functions, or events need to be declared here
        'to make our events work properly in COM
    End Interface
    
    <ComSourceInterfaces(GetType(IFTPEvents)), ClassInterface(ClassInterfaceType.None)> _
    Public Class FTP : Implements IFTP
        Public Event BytesTransferred As BytesTransferredDelegate
        Public Event Uploaded As OnUploaded
    
        Public Sub New()
            'This needed for com interop
        End Sub
    
        Private Sub fCon_BytesTransferred(ByVal sender As Object, ByVal e As EnterpriseDT.Net.Ftp.BytesTransferredEventArgs) Handles fCon.BytesTransferred
            RaiseEvent BytesTransferred(e.ByteCount, e.RemoteFile)
        End Sub
    
        Private Sub fCon_Uploaded(ByVal sender As Object, ByVal e As EnterpriseDT.Net.Ftp.FTPFileTransferEventArgs) Handles fCon.Uploaded
            RaiseEvent Uploaded()
        End Sub
    
    End Class
    

    Here's the sources I used to solve this problem:
    http://msdn.microsoft.com/en-us/library/dd8bf0x3%28v=VS.90%29.aspx
    http://www.codeproject.com/KB/COM/cominterop.aspx#UnmanagedSinks
    http://www.developerfusion.com/tools/convert/csharp-to-vb/