gmniMigration -- Deprecated

The Collections upgrade described below is a deprecated implementation. A similar feature is implemented using the GenericCollection.dll

Using Standard Migrations

No migration can expect to use all or even most of the original COM components that were used by VB6, and no serious migration can be considered complete before all COM components have been replaced by native .NET code. Finally, since these migrations must be upgraded as they are performed, no two migration teams are likely to agree on the best upgrade path to be taken. The default migrations performed by gmBasic are generic and designed to be compilable even in situations where the target code is not fully mature. Their intent is not to produce final functionally equivalent .NET code, but rather to provide a structured, well-formed starting point.


The Migration.dll dynamic link library distributed with gmBasic can be used to extensively modify the migrations performed. It is primarily focused on collections, but will soon be expanded to deal with other frequently used COM components. The actual specifications of the modifications are placed in an XML script file which is stored at the specified target location of the migration project. Its full name is %targetLocation%\Migration.%sysId%.xml. The initial record for this file must be <Migration> and it must end with </Migration>. Between those two tags are the various declarative specification commands.


In addition to the presence of the Migration specification file, the following statement


<LoadRuntime dllName="Migration.dll" />
must also be included in the translation script.


Code, such as that in the Migration.dll, to be made available to the gmBasic tool set is executed by the tool when certain events occur. A key event is the FinishAnalyser event that is triggered when the underlying code has been completed, but before it is passed to the author for surface-form formulation.

The Collections Command

The Collections command supplies the specifications needed to transform VBA.Collection and Scripting.Dictionary into strong collection classes. The two classes have separate specification subcommands.

The StrongCollections Subcommand

The goal of the StrongCollections subcommand is to transform references to the weakly typed VB6.Collection class into the strongly typed .NET List<T> or .NET Dictionary<Key,TValue> class. This transformation is implemented within the FinishAnalyser event. It begins with symbol table scan and then requires a series of code scans. Simply stated the command takes original VB6 code like


Private col1 As VBA.Collection
Private col2 As VBA.Collection
Private col3 As VBA.Collection
  ...
Private Sub AddItem(ByVal i_Item As String)
   col1.Add(i_Item)
   col2.Add(i_Item, i_Item)
End Sub
Private Sub AddItemWithKey(ByVal i_Key As String, ByVal i_Item As String)
   col3.Add(i_Item,i_Key)
End Sub
Private Sub Class_Initialize()
   col1 = New Collection
   col2 = New Collection
   col3 = New Collection
End Sub
into target .NET code like


private List<string> col1;
private List<string> col2;
private Dictionary <string, string> col3;
  ...
private void AddItem(string i_Item)
{
   col1.Add(i_Item);
   col2.Add(i_Item);
}
private void AddItemWithKey(string i_Key, string i_Item)
{
   col3.Add(i_Key, i_Item);
}
public Class1()
{
   col1 = new List<string>();
   col2 = new List<string>();
   col3 = new Dictionary<string, string>();
}
Of course, there are many other environments in which collections are used, such as for-loops and enumerators that have to be dealt with as well.


To keep this discussion simple, the assumption is made that the target classes are List and Dictionary . In fact, the migration team might well select other target classes. As will be seen below the StrongCollections command gives the user full control over this selection.


The first step in the StrongCollections transformation occurs during the initial pass through the symbol table. It simply involves storing the root offsets of all symbols that are instances of the VBA.Collection class in a simple list, so that they can be easily identified via later steps.


Once all of the collections are known, the second step is the determine the type of the collection values and, if relevant, the type of the collection keys. This information can only be obtained from the adds being made to the collection. The second step, then, scans all of the operation code to find calls to the Add methods of the collections. The arguments of these calls are then examined to determine their types. This type information is then included with the initial root offset information saved in the first step.


Once the type information about each collection is known, the third step is to create individual collection classes for the different type combinations and to associate with them the target names and surface patterns needed for the new classes. The actual operation code patterns are known because in the source all are derived from the VB6.Collection class, but the target classes and the target methods are dependent upon the goals of the migration. It is here that the content of the StrongCollections command becomes important. The command itself has no attributes and it has associated with it two subcommands -- ItemOnly and KeyItem. The first describes the migration of collections that have adds without keys and the second with keys. The overall command is as follows then


<StrongCollections>
   <ItemOnly>
    ...
   </ItemOnly>
   <KeyItem>
    ..
   </KeyItem>
</StrongCollections>
With each of those subcommands three pattern strings must be specified -- NetName, ForEach and ForVariant. The NetName string is the pattern string to be used to form the name of the class. The ForEach string is the surface-form pattern string to be used when the IFS.ForEach operation is encountered for the collection. The default surface form is


 <subcode id="ForEach">
    <vbn role="command" narg="3" code="For Each %1d As %3d In %2d\p"/>
    <csh role="command" narg="3" code="foreach(%3d %1d in %2d)\n{\p"/>
 </subcode>
The ForVariant string is the surface-form pattern string to be used when collection type is variant. The ItemOnly entries that can be used with this example are


<ItemOnly>
   <NetName><![CDATA[List<$1d>
In these patterns the notation $1d refers to the type of the items in the collection. This string is supplied from the type information gathered for the collection. The %(n)d notations are the actual arguments to the IFS.ForEach operation in the target code. The use of the < and > are toxic to the XML syntax; therefore, these relatively complicated strings require the CDATA notation and must all be written on a single line.


The KeyItem entries are similar except that they require two type strings.


<KeyItem>
   <NetName><![CDATA[Dictionary<$1d,$2d>
Here $1d is still the item type string; while $2d is the key type string.


Once the new collection classes have been established, the fourth step is to do a final operation code scan to replace references to the VB6.Collection components with the components defined in the previous step.

The StrongDictionaries Subcommand

The goal of the StrongDictionaries subcommand is to transform references to the weakly typed Scripting.Dictionary class into the strongly typed .NET HashSet<T> or .NET Dictionary<Key,TValue> class. This transformation is implemented within the FinishAnalyser event. It begins with symbol table scan and then requires a series of code scans. The Scripting.Dictionary is described in the interface description file scrrun.dll.xml This transformation assumes that the translation script used a simplified version of the this file that removed the unneeded ByRef parameters from the methods. This allowed the tool to generate simpler code patterns. In particular the class interface IDictionary must be described as shown here.


<class id="IDictionary" parent="IDispatch" default="Item">
   <property id="Count" type="Integer" status="Out"/>
   <property id="CompareMode" type="CompareMethod" status="InOut"/>
   <accessor id="Item" type="Variant">
      <argument id="Key" type="Variant" status="ByVal"/>
   </accessor>
   <method id="Add" type="Void">
      <argument id="Key" type="Variant" status="ByVal"/>
      <argument id="Item" type="Variant" status="ByVal"/>
   </method>
   <method id="Exists" type="Boolean">
      <argument id="Key" type="Variant" status="ByVal"/>
   </method>
   <method id="Items" type="Variant"/>
   <method id="Key" type="Void">
      <argument id="Key" type="Variant" status="ByVal"/>
   </method>
   <method id="Keys" type="Variant"/>
   <method id="Remove" type="Void">
      <argument id="Key" type="Variant" status="ByVal"/>
   </method>
   <method id="RemoveAll" type="Void"/>
   <method id="_NewEnum" type="Object"/>
   <accessor id="HashVal" type="Variant">
      <argument id="Key" type="Variant" status="ByVal"/>
   </accessor>
 </class>
Simply stated this transformation takes original VB6 code like


Private dic1 As Scripting.Dictionary
Private dic2 As Scripting.Dictionary
  ...
Private Sub AddItem(ByVal i_Item As String)
   dic1.Add(i_Item,i_Item)
End Sub
Private Sub AddItemWithKey(ByVal i_Key As String, ByVal i_Item As String)
   dic2.Add(i_Key,i_Item)
End Sub
Private Sub Class_Initialize()
   dic1 = New Dictionary
   dic2 = New Dictionary
End Sub
into target .NET code like


private HashSet<string> dic1;
private Dictionary <string, string> dic2;
  ...
private void AddItem(string i_Item)
{
   dic1.Add(i_Item);
}
private void AddItemWithKey(string i_Key, string i_Item)
{
   dic2.Add(i_Key, i_Item);
}
public Class1()
{
   dic1 = new HashSet<string>();
   col2 = new Dictionary<string, string>();
}
Of course, there are many other environments in which dictionaries are used, such as for-loops and enumerators that have to be dealt with as well.


To keep this discussion simple, the assumption is made that the target classes are HashSet and Dictionary . In fact, the migration team might well select other target classes. As will be seen below the StrongDictionaries command gives the user full control over this selection.


The first step in the StrongDictionaries transformation occurs during the initial pass through the symbol table. It simply involves storing the root offsets of all symbols that are instances or the Scripting.Dictionary class in a simple list, so that they can be easily identified via later steps.


Once all of the dictionary collections are known, the second step is the determine the type of the dictionary values and, if relevant, the type of the dictionary keys. In addition, it is fairly common for dictionaries to be used to store collections. If this is the case for any of the dictionaries, then the reference to the collection class is needed as well. This information can only be obtained from the adds being made to the dictionary. The second step, then, scans all of the operation code to find calls to the Add methods of the dictionaries. The arguments of these calls are then examined to determine their types. This type information is then included with the initial root offset information saved in the first step.


Once the type information about each dictionary is known, the third step is to create individual dictionary classes for the different type combinations and to associate with them the target names and surface patterns needed for the new classes. The actual operation code patterns are known because in the source all are derived from the Scripting.Dictionary class, but the target classes and the target methods are dependent upon the goals of the migration. It is here that the content of the StrongDictionaries command becomes important. The command itself has no attributes and it has associated with it two subcommands -- ItemOnly and KeyItem. The first describes the migration of dictionaries that have adds without keys and the second with keys. The overall command is as follows then


<StrongDictionaries>
   <ItemOnly>
    ...
   </ItemOnly>
   <KeyItem>
    ..
   </KeyItem>
</StrongDictionaries>
With each of those subcommands nine pattern strings must be specified -- NetName, ForEach, ForDecl, ForVariant, VarDecl, Exists, RemoveAll, Keys, and CompareMode. The NetName string is the pattern string to be used to form the name of the class. It is here that the specification of the target class is actually made. The ItemOnly entries that can be used with this example are


<ItemOnly>
   <NetName><![CDATA[HashSet<$1d>
In these patterns the notation $1d refers to the type of the items in the dictionary. This string is supplied from the type information gathered for the dictionary. The %(n)d notations are the actual arguments to the various operations in the target code. The use of the < and > are toxic to the XML syntax; therefore, these relatively complicated strings require the CDATA notation and must all be written on a single line.


The KeyItem entries are similar except that they require two type strings.


<KeyItem>
   <NetName><![CDATA[Dictionary<$1d,$2d>
Here $1d is still the item type string; while $2d is the key type string.


There are four different patterns that have to be associated with For Loops -- ForEach, ForDecl, ForVariant, and VarDecl. There are two typically different operation patterns associated with these loops and the migration of dictionaries that still have variant types is different from those with know types. The two operation patterns -- simple and complex are as follows.


LDA,LDA,TYP,IFS.ForEach
LDA,LDA,LLP,MEM.Child,CUF.Args0,REF,TYP,COL,CNV.CastType,LIC,TYP,IFS.ForVariant
The Keys string is used to replace references the dictionary Keys method. There are two operation contexts


usekeys:  LDA.%dictionary%,LLP.Scripting.IDictionary.Keys,MEM.Child,CUF.Args0,argument,ANA.Indexer
passKeys: LDA.%dictionary%,LLP.Scripting.IDictionary.Keys,MEM.Child,COF.Args0,REF
The Exists string is used to replace references to the dictionary Exists method. The operation context is simply


LDA%dictionary%,LLP.Scripting.IDictionary.Exists,MEM.Child
The RemoveAll string is used to replace references to the dictionary RemoveAll method. The operation context is simply


LDA%dictionary%,LLP.Scripting.IDictionary.RemoveAll,MEM.Child
The CompareMode string is used to replace assignments to the dictionary CompareMode property. The operation context is this


LEV.Nest0,LLP,ARG,LDA.%dictionary%,LLP.Scripting.IDictionary.CompareMode,MEM.Child,STR.AssignValue
Once the new dictionary classes have been established, the fourth step is to do a final operation code scan to replace references to the Scripting.Dictionary components with the components defined in the previous step.

The NotUpgraded Subcommand

Both the StrongCollections and StrongDictionaries migrations depend upon the presence of Add method calls to determine the item and key types for the individual collections. If such method calls can not be found for a given collection or dictionary then it is not upgraded. The NotUpgraded subcommand supplies the format of a message to be displayed and triggers a search for components that have not been upgraded. When there are such present in a given code project, messages are sent to the log file. The command itself is a singleton content only command. It might be as follows.


<NotUpgraded>UPGRADE_TODO: $1d $2d cannot be upgraded to generic.</NotUpgraded>
The $1d substring is the type name of the component that was not upgraded and the $2d substring is the fully qualified identifier of that component. A sample output would be


UPGRADE_TODO: Scripting.Dictionary SQLDiff.clsIndices.mdicIndices cannot be upgraded to generic.
UPGRADE_TODO: Scripting.Dictionary SQLDiff.clsFKeys.m_FKeyDic cannot be upgraded to generic.
Note that there are scripting commands CollectionAdd and MigrationAdd available to supply the missing information for particular code projects.

Scripting Commands

The migrations of particular components within particular source code projects do not always succeed, because the information needed to perform them is either not available of not in the expected form. The tool supports an ExecuteCommand event which is triggered whenever the tool encounters a script command at the primary level of a command script that it does not recognize. This event can be used to supply additional information to the migrations of particular code projects within the translation scripts for those projects.

The DictionaryAdd Scripting Command

The DictionaryAdd scripting command supports the Collections.StrongDictionaries migration. That migration depends upon finding calls to this method in the code.


<method id="Add" type="Void">
   <argument id="Key" type="Variant" status="ByVal"/>
   <argument id="Item" type="Variant" status="ByVal"/>
</method>
The key type is derived from the actual type of the Key argument and if the Item argument is specified then the item type is derived from it; else it is unspecified. It is these two types that define the type of the host dictionary.


The DictionaryAdd script command has three attributes as follows:


Attribute Description
IdentifierSpecifies the fully qualified name of a dictionary component in the code project.
KeySpecifies the key type for the dictionary.
ItemIf present, specifies the item type for the dictionary.

If the identifier cannot be located, then the command is simply ignored. As an example assume that a translation script whose Collections specification contained a NotUpgraded subcommand, sent this message to the log


UPGRADE_TODO: Scripting.Dictionary SQLDiff.GenerateScript.dicGenerateIndex cannot be upgraded to generic.
then the following command added to the translation script


</Compile>
<DictionaryAdd identifier="SQLDiff.GenerateScript.dicGenerateIndex" Key="String" Item="Boolean" />
<Analyse />
would allow that component to be upgraded and would then cause changes like the following


Comparing files ..\SQLDiff.bnd and ..\SQLDIFF.SAV
***** ..\SQLDiff.bnd
Dictionary<string, bool> generateIndex;
generateIndex = new Dictionary<string, bool>();
generateIndex[strIndexName] = true;
***** ..\SQLDIFF.SAV
Scripting.Dictionary generateIndex;
generateIndex = new Scripting.Dictionary();
generateIndex.set_Item(strIndexName, true);
Note that the actual DictionaryAdd commands must follow the Compile and precede the Analyse statements.

The CollectionAdd Scripting Command

The CollectionAdd scripting command supports the Collections.StrongCollections migration. That migration depends upon finding calls to this method in the code.


<method id="Add" type="String" opcode="COL.1">
   <argument id="Item" type="Variant" status="ByVal"/>
   <argument id="Key" type="String" status="ByVal" optional="DEF.NullObject"/>
   <argument id="Before" type="Variant" status="ByVal" optional="%15%"/>
   <argument id="After" type="Variant" status="ByVal" optional="%15%"/>
</method>
The item type is derived from the actual type of the Item argument and if the Key argument is specified then the key type is derived from it; else it is unspecified. It is these two types that define the type of the host collection.


The CollectionAdd script command has three attributes as follows:


Attribute Description
IdentifierSpecifies the fully qualified name of a collection component in the code project.
ItemSpecifies the item type for the collection.
KeyIf present, specifies the key type for the collection.

If the identifier cannot be located, then the command is simply ignored. Note that the actual CollectionAdd commands must follow the Compile and precede the Analyse statements.
Table of Contents