Support Statement: Techniques for Weak Typing and ref Tempargs

Introduction

VB6 will implicitly convert just about anything to anything.  Also VB6 has various weak types: Object, Variant, Form, Control, as well as a weakly typed collection class, that allow programmers to defer type checks until runtime.  And, many of the COM APIs used by VB6 and ASP are exposed through weakly typed interfaces. To make matters worse, VB6 uses the same notation for late calls and early calls.  The details are complex, but they are handled by the VB6/COM runtime behind the scenes.  

On the other hand, the .NET languages, tend to favor strong type checking at compile time, and making late calls in .NET typically requires deliberate use of object-oriented design, abstraction, interfaces, and late-call syntax.

In general, gmStudio attempts to upgrade Variants, and to a lesser degree Objects, to strong types by analyzing usage of the weakly typed symbols in the context of all types defined to the source system.  In most cases this produces cleaner, more correct, strongly typed code.  However, in certain situations, usage information is insufficient or ambiguous; so, selecting a single strong type is not possible and  the upgraded code will retain the weaker types and have to handle multiple types later.  In these situations, the tool must use techniques like CallByName, the dynamic type, temporary variables, and casting to make the weakly-typed code build in .NET.  But, although the resulting weakly-typed code builds, it may not run properly.    Furthermore, some VB6 applications use weakly typed code very deliberately to implement more dynamic logic.  When this is done, the upgraded code usually also needs to be dynamic.

To help teams deal with these typing related upgrade issues,  gmStudio provides features for implementing an upgrade solution that either uses stronger types or implements more dynamic logic, depending on what is required.  The commands for introducing stronger types are registry/fixtype, refactor/fixtype, modifying IDFs to have stronger types, and in some cases using pre-edits in the translation scripts to modify the original code.  Some of the commands for allowing dynamic types are:

  • refactor/generic
  • refactor/overload
  •  use of the dynamic metatype
  • dictionaryAdd/CollectionAdd
  • and many others. 

This article presents the various techniques for improving how your solution upgrades weakly typed symbols and the code that uses them.

ByRef object arguments taking with multiple types

Consider argument rvarItem in the following example.  It is a ByRef Variant argument that can be used to pass different types of variables back from the function g_Collection_ItemIsDefined:

Public Function g_Collection_ItemIsDefined(ByVal vobjCollection As Object, Optional ByVal vvarItem As Variant, Optional ByVal vstrKey As String, _
    Optional ByRef rvarItem As Variant) As Boolean

Suppose the function g_Collection_ItemIsDefined is called with many different types of variables passed to the rvarItem.  In this case, the tool will declare the formal argument as type object to allow for flexibility.  However, passing an argument by ref in .NET requires strict typing.  The only quantity that can be passed to a ref object is something explicitly declared as object.  To allow the upgraded code to build within this constraint, the tool must introduce a temporary variable to box the actual arguments.  These temporary variables are named argTemp1, argTetmp2... argTempN with numbering starting at 1 within each file.  For example, passing a boolean to a ref object looks like this:

Introduction of a tempArg
VB6:
If g_Collection_ItemIsDefined(colItemOperations, , strOperation, blnOperationEnabled) ...


C#
object argTemp1 = blnOperationEnabled;
if (basCollection.g_Collection_ItemIsDefined(colItemOperations,null,strOperation,ref argTemp1)) ...

By default, the argTemp variables introduced for parameters are ONLY initialized from the actual argument used in the call. They are not moved back to the actual argument after the call. In many cases this does not matter, but sometimes ignoring the change to a ByRef argument is a breaking change from the VB6 that must be addressed using various techniques depending on the situation. 


One possible technique for eliminating the temporary variables is to use the Refactor/Overload command.  Refactor/Overload is a translation script command (gmPL) that may be be added to the Compile block in your translation script as in the following example:

...
<Compile Project="..." >
<Refactor>
<OverLoad Identifier="Project1.Form1.MySub.o_Var" Types="Boolean" />
</Refactor>
</Compile>
...
where
Identifier  is the fully qualified identifier of the argument to overload
Types       is a list of source types known to the tool.  This can include gmStudio's VB6 types, application types, and external COM types.  
            Types may also be the keyword on, which indicates that the tool should analyze and report the overloading still needed for the argument.

The effect of adding a refactor/overload command is three-fold:

1) The tool will report warnings about other types used with the argument.  These warnings can be used to add more types to the Types list. For example:

Warning#3005: Passing <App.frmClientGroupSelect.m_DisplayedClientGroupColumns_AdjustColumnHeaders.chdSortColumnHeader> with type <MSComctlLib.ColumnHeader> to <App.g_Collection_ItemIsDefined.rvarItem> with type <Variant>
Warning#3005: Passing <App.frmRecipeBookSelect.m_DisplayedRecipeBookColumns_AdjustColumnHeaders.chdSortColumnHeader> with type <MSComctlLib.ColumnHeader> to <App.g_Collection_ItemIsDefined.rvarItem> with type <Variant>
Warning#3005: Passing <App.frmFoodClassSelect.m_FoodClass_Populate.nodFoodClass> with type <MSComctlLib.Node> to <App.g_Collection_ItemIsDefined.rvarItem> with type <Variant>
Warning#3005: Passing <App.frmFoodClassSelect.m_FoodClass_PopulateChildren.nodParentFoodClass> with type <MSComctlLib.Node> to <App.g_Collection_ItemIsDefined.rvarItem> with type <Variant>
Warning#3005: Passing <App.frmClientSelect.m_Clients_Populate.lsiClient> with type <MSComctlLib.ListItem> to <App.g_Collection_ItemIsDefined.rvarItem> with type <Variant>
Warning#3005: Passing <App.frmClientSelect.m_DisplayedClientColumns_AdjustColumnHeaders.chdSortColumnHeader> with type <MSComctlLib.ColumnHeader> to <App.g_Collection_ItemIsDefined.rvarItem> with type <Variant>
....
Warning#3005: Passing <App.g_VBObject_SetEnabled.colContainerContents> with type <Collection> to <App.g_Collection_ItemIsDefined.rvarItem> with type <Variant>
Warning#3005: Passing <App.g_VBObjects_LoadContainers.colContainerContents> with type <Collection> to <App.g_Collection_ItemIsDefined.rvarItem> with type <Variant>
Warning#3005: Passing <App.g_DragCursor_Set.colItemTypeItems> with type <Collection> to <App.g_Collection_ItemIsDefined.rvarItem> with type <Variant>
Warning#3005: Passing <App.g_RecentItems_Add.strRecentItem> with type <String> to <App.g_Collection_ItemIsDefined.rvarItem> with type <Variant>

2) The tool will generate an overload of the method for each type in Types.  These overloads deal with marshaling different variables to the weakly-typed formal argument in the implementation.  Notice that in the wrapper, the temporary argument is moved back to the actual arg after the call. For example:

      public static bool g_Collection_ItemIsDefined(object vobjCollection,object vvarItem,string vstrKey,ref bool rvarItem)
      {
         object TemporaryArg1 = rvarItem;
         bool retVal = g_Collection_ItemIsDefined(vobjCollection,vvarItem,vstrKey,ref TemporaryArg1);
         rvarItem = Convert.ToBoolean(TemporaryArg1);
         return retVal;
      }

3) The tool  removes the use of the ref tempArg from the calls where an actual arg of one of the types in Types is passed.  For example, the code using argTemp1 shown above becomes simply:

if (basCollection.g_Collection_ItemIsDefined(colItemOperations,null,strOperation,ref blnOperationEnabled))

 

Refactor Generic

<Compile Project="..." >
<Refactor>
<Generic identifier="Project1.Form1.MySub.o_Var" Types="Integer,String" />
</Refactor>
</Compile>
 

 

Â