VB6 event hookups dissappearing after X iterations, then throwing RCW error on event disconnect
-
Thursday, March 26, 2009 3:15 PM
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
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 SubThanks,
-Melody
Melody @ Summsoft- Marked As Answer by MelodyAtPeopleSystems Wednesday, April 15, 2009 6:09 PM
All Replies
-
Monday, March 30, 2009 10:06 AMModeratorWhy 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 PMRiquel,
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 AMModeratorHi 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
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 AMModeratorHi 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 PMRiquel,
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 PMModeratorHi 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 PMRiquel,
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
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 SubThanks,
-Melody
Melody @ Summsoft- Marked As Answer by MelodyAtPeopleSystems Wednesday, April 15, 2009 6:09 PM

