Recently one of our customers faced a critical issue with COM classes in a converted application. The original VB6 application created instances of external classes at runtime that executed methods returning objects consumed by the application to populate grids, insert/edit data, and save them into a database. The migration of the VB6 app to C# required some problem solving to deal with this aspect of the source code.
The following VB6 code mirrors the issue: A COM class MyComClass registered in a server is instantiated and its DoSearch() method is invoked. At runtime, this method returns an ADODB Recordset that is used to populate a grid.
Dim obj As Variant Set obj = CreateObject("MyComClass", "myServer") Dim rs As ADODB.Recordset Set rs = obj.DoSearch(strCriteria) Set DataGrid1.DataSource = rs
That VB6 code uses late binding to create a MyComClass object and then call DoSearch(). While it works very nicely in VB6, the conversion to .NET is tricky.
When a new Upgrade project is created using the VBUC (here you can take a look at the VBUC Quick start guide ) by default ADODB is converted to ADO.NET using System.Data.Common and helper classes.
This upgrade option implies ADODB.Recordset objects are replaced by Mobilize Enhanced DataSet (ADORecordsetHelper) objects.
For the above code in VB6, the generated code will look like:
object obj = Activator.CreateInstance(Type.GetTypeFromProgID("MyComClass"), "myServer", true); ADORecordSetHelper rs = ReflectionHelper.Invoke<ADORecordSetHelper>(obj, "DoSearch", new object[]{strCriteria}); DataGrid1.DataSource = rs.Tables[0];
Notice how ADODB.Recordset was converted into a ADODBRecordsetHelper (the Mobilize Enhanced DataSet that emulates classic Recordset functionality).
Also, due to the usage of late binding in VB6, the converted code is using Mobilize ReflectionHelper to execute obj.DoSearch(strCriteria.). More info about late-binding and ReflectionHelper here.
Although the converted code compiles, a runtime exception is thrown here:
ADORecordSetHelper rs = ReflectionHelper.Invoke<ADORecordSetHelper>(obj, "DoSearch", new object[]{strCriteria});
The exception is caused because .NET cannot cast a COM object (in this case ADODB.Recordset) into a .NET DataSet class.
The following summarizes the situation at this point:
At this point there are different approaches to addressing this issue, but all of them require analyzing the code to understand how ADODB.Recordsets returned by COM functions like DoSearch() interact with other portions of the original VB6 and migrated code.
This approach requires reconverting the code and keeping ADODB via COM Interop in .NET.
The code at the beginning of this post would look like this:
object obj = Activator.CreateInstance(Type.GetTypeFromProgID("MyComClass"), "myServer", true); ADODB.Recordset rs = ReflectionHelper.Invoke<ADODB.Recordset>(obj, "DoSearch", new object[]{strCriteria});
In this scenario, we're still using ADODB via COM Interop in .NET, and the code will work in .NET. However, there may be potential issues with this approach:
DataGrid1.DataSource = rs
where rs is an ADODB.Recordset.
Despite this working in VB6, if the DataGrid is migrated and converted to a .NET DataGridView, the equivalent conversion for the previous VB6 line of code will do nothing:
DataGrid1.DataSource = rs;
This approach could be cheaper to solve the issue described here, but definitely is just a manual coding workaround that may be challenged with other coding scenarios.
This is how the VBUC converts the VB6 code indicated above, but we know this crashes at runtime:
object obj = Activator.CreateInstance(Type.GetTypeFromProgID("MyComClass"), "myServer", true); ADORecordSetHelper rs = ReflectionHelper.Invoke<ADORecordSetHelper>(obj, "DoSearch", new object[]{strCriteria}); DataGrid1.DataSource = rs.Tables[0];
The above code with a few manual changes could be functional.
object obj = Activator.CreateInstance(Type.GetTypeFromProgID("MyComClass"), "myServer", true); ADORecordSetHelper rs; ADODB.Recordset rs2 = ReflectionHelper.Invoke<ADORecordSetHelper>(obj, "DoSearch", new object[]{strCriteria}); System.Data.OleDb.OleDbDataAdapter da = new System.Data.OleDb.OleDbDataAdapter(); da.Fill(rs, rs2, "customer"); DataGrid1.DataSource = rs.Tables[0];
Perhaps the best option in terms of getting rid of legacy code: convert both the VB6 application consuming COM classes and the COM classes as well.
If the source code (for the COM classes) is not available, then consider replacing COM classes with a .NET alternative.
Although this approach might extend the scope of the migration project, it eliminates dealing with issues like the one described in this post. Further, it will have a positive impact on the stabilization phase of the project (for more info on the stages of a migration project you can read this).
This type of situation is hard to solve when converting code, so I recommend understanding the architecture of the VB6 application being converted before starting any upgrade process.
If COM objects are used to save data manipulated by the migrated application back into database, then I suggest using approach 1 or 3. It's tempting to try coding a new method that copies back data from the ADO.NET DataSet to a ADODB.Recordset, but keep in mind those data containers also have information about row states and other info that could not be possible to pass to a ADODB Recordset, creating other challenging issues.
Approach #3 is my favorite one as it will prevent major issues later during the stabilization phase.