Visual Studio Developer Center > Visual Basic Forums > Visual Basic Interop and Upgrade > VB6 event hookups dissappearing after X iterations, then throwing RCW error on event disconnect

Answered VB6 event hookups dissappearing after X iterations, then throwing RCW error on event disconnect

  • Thursday, March 26, 2009 3:15 PM
     
      Has Code
    Hello all,
    I have run into a problem with VB6 events and (VB.Net) System.Windows.Forms. 

    I have a VB6 application and VB .Net library.  The VB .Net library hooks into events of the VB6 application.  For one event, a System.Windows.Forms.Form is displayed.  This works for X number of times (X varies from machine to machine), but then the .Net library no longer catches the VB6 event.  Once the event is no longer caught, trying to access any events from the VB6 app in the VB.Net library (Add or RemoveHandler) results in the error:

    System.Runtime.InteropServices.InvalidComObjectException was caught
      Message="COM object that has been separated from its underlying RCW cannot be used."
      Source="mscorlib"
      StackTrace:
           at System.Runtime.InteropServices.ComTypes.IConnectionPoint.Unadvise(Int32 dwCookie)
           at MyAppVB6_Phase1.__Document_EventProvider.remove_KeyPress(__Document_KeyPressEventHandler )
           at MyAppVB6_Phase1.DocumentClass.remove_KeyPress(__Document_KeyPressEventHandler )
           at VSTAIntegration.ApplicationIntegration_Phase1.Disconnect()


    This occurs when I create OR display any System.Windows.Forms in the event including a MessageBox or a form with DisplayDialog.  I can also get this error by calling System.GC.Collect() after the VB.Net library has caught the VB6 event.  The workarounds I have tried which do not help include: calling Dispose on the form, implementing a using statement, and displaying the SWF on a separate thread.  

    I am including the relevant code from the VB6 app and the VB.Net library.  Please let me know if additional code is needed.

    Is this a known issue?  Is there a workaround?  Do you have any suggestions?

    VB6 Application:
    Event KeyPress(ByRef KeyCode As Integer
     
    'This routine is called to tell VBA that the event occurred 
    Friend Sub KeyPress(ByRef KeyCode As Integer
     
        RaiseEvent KeyPress(KeyCode) 
         
    End Sub 

    VB.Net Library:
    Public Class VbNetLib 
     
        Private hostApp As MyAppVB6_Phase1.Application 'VB6 app 
        Private testForm1 As frmTest  'form for testing 
        Private count As Integer = 0  'count to tract hits 
        Private testCase As Integer   'test case to use 
     
        Public Sub Connect(ByVal hostApplication As Object
     
            'get the VB6 app 
            Me.hostApp = CType(hostApplication, MyAppVB6_Phase1.Application) 
     
            'hook into the event 
            AddHandler Me.hostApp.Document.KeyPress, AddressOf KeyPressEventTest 
     
            'set the case to test 
            testCase = 0 
     
            'create a form for test case 3 
            Me.testForm1 = New frmTest() 
     
        End Sub 
     
        Public Sub Disconnect() 
            RemoveHandler Me.hostApp.Document.KeyPress, AddressOf KeyPressEventTest  'will cause RCW error if events unhooked 
        End Sub 
     
        Private Sub KeyPressEventTest(ByRef keyCode As Short
            count += 1 
            Me.hostApp.Document.Text = count 
     
            Select Case testCase 
     
                Case 0 'Show message box - about 500 hits 
                    MsgBox(count) 
     
                Case 1 'Create form in hit and display - about 80 hits 
     
                    'create and display a form 
                    Dim testFormX As frmTest = New frmTest() 
                    testFormX.ShowDialog() 
     
                    If testFormX.DialogResult = Windows.Forms.DialogResult.OK Then 
                        'do something 
                    End If 
     
                    'dispose of the form 
                    testFormX.Close() 
                    testFormX.Dispose() 
                    testFormX = Nothing 
     
                Case 2 'display a form- about 120 hits 
     
                    'display the form 
                    Me.testForm1.ShowDialog() 
     
                    If testForm1.DialogResult = Windows.Forms.DialogResult.OK Then 
                        'do something 
                    End If 
     
                    'hide the form 
                    testForm1.Hide() 
     
                Case 3 'create a form but do not display it- about 400 hits 
     
                    'create a form 
                    Dim testFormX As frmTest = New frmTest() 
     
                    'dispose of the form 
                    testFormX.Close() 
                    testFormX.Dispose() 
                    testFormX = Nothing 
     
                Case 4 ' call System.GC.Collect- always only 1 hit 
                    System.GC.Collect() 
     
            End Select 
     
        End Sub 
     
    End Class 


    Any help is much appreciated.
    Thanks,
    -Melody

    Melody @ Summsoft

Answers

  • Wednesday, April 15, 2009 6:08 PM
     
     Answered Has Code
    Riquel,

    Thank you for looking into this.  We have found two work arounds, both are below.  The first workaround keeps a local copy of the object which is the source of the events instead of relying on the parent object, although you can still hook into the events through the parent object.  The second workaround manually calls advise and unadvise for the COM event source connection point.

    Workaround 1- keep a local copy of the event source instead of using the parent object
        Dim hostApp As MyAppVB6_EventTest.Application
        Private testForm1 As frmTest  'form for testing
        Private count As Integer = 0  'count to tract hits
        Private testCase As Integer   'test case to use
    
        'WORKAROUND
        Private hostDoc As MyAppVB6_EventTest.Document 'local copy of object to hook into
    
    
        Public Sub New()
            MyBase.New()
    
        End Sub
    
        Public Sub Connect(ByVal hostApplication As Object)
    
            'get the VB6 app
            Me.hostApp = CType(hostApplication, MyAppVB6_EventTest.Application)
    
            'save the object to hook into
            hostDoc = Me.hostApp.Document
    
            'hook into the event
            AddHandler Me.hostApp.Document.KeyPress, AddressOf KeyPressEvent
    
        End Sub
    
        Public Sub Disconnect()
            Try
                RemoveHandler Me.hostApp.Document.KeyPress, AddressOf KeyPressEvent
            Catch ex As Exception
                MsgBox("Error unhooking event: " & ex.ToString())
            End Try
    
        End Sub
    
        Public Sub KeyPressEvent(ByRef keyCode As Short)
            count += 1
            Me.hostApp.Document.Text = count
    
            'do something with GC or System.Windows.Forms
            MsgBox(count)
    
        End Sub
    
    

    Workaround 2- manually call advise and unadvise for the COM event source connection point
        Dim hostApp As MyAppVB6_EventTest.Application
        Private testForm1 As frmTest  'form for testing
        Private count As Integer = 0  'count to tract hits
        Private testCase As Integer   'test case to use
    
        'WORKAROUND 2
        'SWF event workaround
        Dim icp As System.Runtime.InteropServices.UCOMIConnectionPoint
        Dim cookie As Integer = -1
    
        Public Sub New()
            MyBase.New()
    
        End Sub
    
        Public Sub Connect(ByVal hostApplication As Object)
    
            'get the VB6 app
            Me.hostApp = CType(hostApplication, MyAppVB6_EventTest.Application)
    
            'WORKAROUND 2
            'SWF event workaround
            'Call QueryInterface for IConnectionPointContainer
            Dim icpc As System.Runtime.InteropServices.UCOMIConnectionPointContainer = _
                CType(Me.hostApp.Document, System.Runtime.InteropServices.UCOMIConnectionPointContainer)
            'Find the connection point for the __Document source events
            Dim g As Guid = GetType(MyAppVB6_EventTest.__Document).GUID
            icpc.FindConnectionPoint(g, icp)
            'Pass a pointer to the host to the connection point
            icp.Advise(Me, cookie)
    
    
            'hook into the event
            AddHandler Me.hostApp.Document.KeyPress, AddressOf KeyPressEvent
    
        End Sub
    
        Public Sub Disconnect()
    
            'WORKAROUND 2
            'SWF event workaround
            'End the connection
            If cookie <> -1 Then icp.Unadvise(cookie)
    
            Try
                RemoveHandler Me.hostApp.Document.KeyPress, AddressOf KeyPressEvent
            Catch ex As Exception
                MsgBox("Error unhooking event: " & ex.ToString())
            End Try
    
        End Sub
    
        Public Sub KeyPressEvent(ByRef keyCode As Short)
            count += 1
            Me.hostApp.Document.Text = count
    
           'Do something with GC or System.Windows.Forms
            MsgBox(count)
    
        End Sub
    Thanks,
    -Melody

    Melody @ Summsoft

All Replies

  • Monday, March 30, 2009 10:06 AM
    Moderator
     
     
    Why do you not create one .NET classlibrary which exposes one method to let VB6 call and execute the code in .NET library in this scenario?

    Dim c1 As Riquel_test_form_vb6.ComClass1
    Private Sub Command1_Click()
    c1.test
    End Sub
    Private Sub Form_Load()
    Set c1 = New Riquel_test_form_vb6.ComClass1
    End Sub


    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    Welcome to the All-In-One Code Framework! If you have any feedback, please tell us.
  • Monday, March 30, 2009 2:13 PM
     
     
    Riquel,
    Thanks for you reply.  The purpose of subscribing to the events is an extensibility scenario where the host would not have knowledge of the .Net libraries which are hooking into the event.  So if the VB6 host had to call into the .Net libraries when the event is raised that would defeat the purpose of extensibility.  Is there any way to address this problem directly?

    Thanks!
    -Melody
    Melody @ Summsoft
  • Tuesday, March 31, 2009 9:07 AM
    Moderator
     
     
    Hi Melody,

    COM project libraries are created by programs such as C++ or Visual Basic 6.0 and rely on an object reference counting scheme for maintaining and releasing object references. When an object's reference count reaches zero, it is immediately released. The .NET Framework however, uses a managed memory scheme whereby live references are periodically traced from the roots, to other referenced objects, in a tree like structure; when completed, all unreachable objects are released.

    Let me know your scenario about how you use .NET component in VB6 application.

    1. How does your VB6 application use that .NET component? Do you declare one CCW variable in your scenario?

    Riquel
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    Welcome to the All-In-One Code Framework! If you have any feedback, please tell us.
  • Tuesday, March 31, 2009 2:15 PM
     
      Has Code
    Riquel,
    Thanks for the info. 

    >Let me know your scenario about how you use .NET component in VB6 application.
    This is part of a VSTA integration.  In this case the host application is written in VB6.  The VB .Net library is referenced by the VB6 host and accepts an instance of the host object, in this case the MyAppVB6.Application class.  This instance of the host object is then passed to VSTA add-ins through a HostItemProvider .  In this case, the add-ins are directly referencing the host instead of using a proxy (although I believe this repro's using a proxy as well).  The VSTA add-ins then hook into events from the host object.

    So the VB6 host application only has knowledge of the VB.Net integration library.  The repro case I gave above has the VB.Net library hooking into the host event instead of passing the host object to the add-ins through the HostItemProvider.  It is much easier to see the error directly through the VB.Net library which the VB6 host hooks into than tracing it through add-ins. 

    >1. How does your VB6 application use that .NET component? Do you declare one CCW variable in your scenario?
    Yes, the VB6 host declares on instance of the VB.Net library (CCW) and passes the application object to it.  The CCW is set on form load and released on form unload.  The MyAppVB6.Application class is created and released similarly. 

    Here is code from the main VB6 form:
    'Phase1 Integration Public AppIntegration As VSTAIntegration_Phase1.ApplicationIntegration_Phase1

    Private Sub Form_Load()

    'Phase1 Integration
    'Setup the AppIntegration
    Set AppIntegration = New ApplicationIntegration_Phase1

    'load add-ins
    AppIntegration.Connect AppObj

    End Sub

    Private Sub Form_Unload(Cancel As Integer)
    'Phase1 Integration
    'unload add-ins
    AppIntegration.Disconnect
    Set AppIntgration = Nothing

    End Sub

    'From ModMain:
    Public AppObj As Application

    Have you been able to reproduce this?  If you like I could set up a ftp link with the repro case.
    Is there a known issue with .Net libraries hooking into VB6 events, and if so is there a workaround?
    Thanks,
    -Melody
    Melody @ Summsoft
  • Wednesday, April 01, 2009 7:00 AM
    Moderator
     
     
    Hi Melody,

    I don't use VSTA previously. It looks very interesting. I need some time to understand it. Just based on your scenario, I think that RCW isnot reachable, so GC collected this object to raise this error. You can use SOS to confirm this to us.

    You can use VS debugger with SOS to check whether the object is alive. Have a look at !gcroot and !dumpheap to investigate current issue.

    You also can send your demo to me currently to let me reproduce your scenario after I read VSTA in MSDN.

    v-ridong at microsoft dot com

    Riquel
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    Welcome to the All-In-One Code Framework! If you have any feedback, please tell us.
  • Monday, April 13, 2009 8:26 PM
     
     
    Riquel,
    Were you able to reproduce the error based on the case I e-mailed you?  Do you have any suggestions?

    Thanks,
    -Melody
    Melody @ Summsoft
  • Tuesday, April 14, 2009 12:01 PM
    Moderator
     
     
    Hi Melody,

    Recently I am busy. Don't read VSTA currently. Just start(Debuging or without Debugging) to see MyApp6 Window. Then press Enter key to see following text:

    94key Press 2 Event

    Don't get exception. My environment is: Vista 64bit +VS2008. Can't test your project in my production computer(which is 32bit Windows2008).

    Best regards,
    Riquel

    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    Welcome to the All-In-One Code Framework! If you have any feedback, please tell us.
  • Tuesday, April 14, 2009 1:48 PM
     
     
    Riquel,
    Sorry to hear you are busy and thank you for taking the time to look into this.  I pulled all VSTA out of the repro case so that shouldn't be an issue.  For some of the test cases 94 isn't enough iterations to throw the error.  In the KeyPressEvent method I included comments on the number of iterations required to see the error for each test case.  These are based on a 32 bit  environment, I'm not sure if using a 64 bit would change that- if anything I'd expect it would simply take more cycles to cause the problem.  Did you try case 4- it calls GC.Collect and always was a problem for me after only one cycle.  The problem manifests in two ways- the event is not hit and when you terminate the VB6 application (X out of it, don't stop debugging through VS) the RCW error occurs.

    I can test this out in a 64 bit vista environment and post the results.

    Thanks again!
    -Melody
    Melody @ Summsoft
  • Wednesday, April 15, 2009 6:08 PM
     
     Answered Has Code
    Riquel,

    Thank you for looking into this.  We have found two work arounds, both are below.  The first workaround keeps a local copy of the object which is the source of the events instead of relying on the parent object, although you can still hook into the events through the parent object.  The second workaround manually calls advise and unadvise for the COM event source connection point.

    Workaround 1- keep a local copy of the event source instead of using the parent object
        Dim hostApp As MyAppVB6_EventTest.Application
        Private testForm1 As frmTest  'form for testing
        Private count As Integer = 0  'count to tract hits
        Private testCase As Integer   'test case to use
    
        'WORKAROUND
        Private hostDoc As MyAppVB6_EventTest.Document 'local copy of object to hook into
    
    
        Public Sub New()
            MyBase.New()
    
        End Sub
    
        Public Sub Connect(ByVal hostApplication As Object)
    
            'get the VB6 app
            Me.hostApp = CType(hostApplication, MyAppVB6_EventTest.Application)
    
            'save the object to hook into
            hostDoc = Me.hostApp.Document
    
            'hook into the event
            AddHandler Me.hostApp.Document.KeyPress, AddressOf KeyPressEvent
    
        End Sub
    
        Public Sub Disconnect()
            Try
                RemoveHandler Me.hostApp.Document.KeyPress, AddressOf KeyPressEvent
            Catch ex As Exception
                MsgBox("Error unhooking event: " & ex.ToString())
            End Try
    
        End Sub
    
        Public Sub KeyPressEvent(ByRef keyCode As Short)
            count += 1
            Me.hostApp.Document.Text = count
    
            'do something with GC or System.Windows.Forms
            MsgBox(count)
    
        End Sub
    
    

    Workaround 2- manually call advise and unadvise for the COM event source connection point
        Dim hostApp As MyAppVB6_EventTest.Application
        Private testForm1 As frmTest  'form for testing
        Private count As Integer = 0  'count to tract hits
        Private testCase As Integer   'test case to use
    
        'WORKAROUND 2
        'SWF event workaround
        Dim icp As System.Runtime.InteropServices.UCOMIConnectionPoint
        Dim cookie As Integer = -1
    
        Public Sub New()
            MyBase.New()
    
        End Sub
    
        Public Sub Connect(ByVal hostApplication As Object)
    
            'get the VB6 app
            Me.hostApp = CType(hostApplication, MyAppVB6_EventTest.Application)
    
            'WORKAROUND 2
            'SWF event workaround
            'Call QueryInterface for IConnectionPointContainer
            Dim icpc As System.Runtime.InteropServices.UCOMIConnectionPointContainer = _
                CType(Me.hostApp.Document, System.Runtime.InteropServices.UCOMIConnectionPointContainer)
            'Find the connection point for the __Document source events
            Dim g As Guid = GetType(MyAppVB6_EventTest.__Document).GUID
            icpc.FindConnectionPoint(g, icp)
            'Pass a pointer to the host to the connection point
            icp.Advise(Me, cookie)
    
    
            'hook into the event
            AddHandler Me.hostApp.Document.KeyPress, AddressOf KeyPressEvent
    
        End Sub
    
        Public Sub Disconnect()
    
            'WORKAROUND 2
            'SWF event workaround
            'End the connection
            If cookie <> -1 Then icp.Unadvise(cookie)
    
            Try
                RemoveHandler Me.hostApp.Document.KeyPress, AddressOf KeyPressEvent
            Catch ex As Exception
                MsgBox("Error unhooking event: " & ex.ToString())
            End Try
    
        End Sub
    
        Public Sub KeyPressEvent(ByRef keyCode As Short)
            count += 1
            Me.hostApp.Document.Text = count
    
           'Do something with GC or System.Windows.Forms
            MsgBox(count)
    
        End Sub
    Thanks,
    -Melody

    Melody @ Summsoft