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 |
Identifier | Specifies the fully qualified name of a dictionary component in the code project. |
Key | Specifies the key type for the dictionary. |
Item | If 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 |
Identifier | Specifies the fully qualified name of a collection component in the code project. |
Item | Specifies the item type for the collection. |
Key | If 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.