gmplCallByNameStatement

CallByName Statement Summary

CallByName is a terminal, refactoring statement that occurs only within a Refactor statement. The refactoring done by this statement takes a different view of refactoring, since it is not symbols that are being refactored but rather symbol-related code events that must be trapped and then refactored. In particular, before the compiler generates a late-binding callbyname operation on a symbol it checks if that symbol has a CallByName refactoring boxing type associated with it. If so, it boxes the symbol to that type and attempts to perform a normal early-bound call. Removing CallByName is performed by the compiler and not by the analyser.

As discussed in the "Anatomy of CallByName" the alternative way of removing late-binding is to strengthen the type of the host symbol or to use the newer .NET type Dynamic. CallByName refactoring is used when changing the type of a symbol is not workable or desirable.

The attributes of the CallByName statement are as follows:

Attribute Description
Source This optional attribute specifies the component whose code is generating late-bound calls by name. If the containing Refactor statement had a FileFilter specified then this identifier should be specified relative to it; else it should be specified relative to the root of the symbol table. If this attribute is omitted, then the code source is assumed to be the parent of the host symbol.
Host This required attribute specifies the host symbol, the actual object instance that is exposing the method or property being called. If the containing Refactor statement had a FileFilter specified then this identifier should be specified relative to it; else it should be specified relative to the root of the symbol table. There are three special identifiers that can be used at the start of this attribute: *Project, *Item, and *Dim.identifier.
BoxType This required attribute contains a comma-delimited list of the boxing types that may be used to resolve the late binding. gmBasic checks each type entry to see if it has a component that matches the following method or property name. If none exists, it does a box to the last type in the list regardless of whether it still generates a late-binding. There are three special identifiers that can be used with the type entries: *None, *Project, and *Self.
Context This optional attribute specifies the context in which the late call can occur: LateCall, LateCollection, and LateDefault.

The special identifier *Project says to start the symbol search with the parent of the FileFilter. This will be the project file containing file whose symbols are being refactored. There are many different types of collections, the *Item identifier specifies any collection class Item method. The *Dim.identifier is necessary because these specifications are processed before any procedural code has been processed; therefore, undeclared variables are not yet in the symbol table. The *Dim simply specifies that the identifier needs to be declared. The *None type is used while these specifications are being developed. Where late calls are being generated is easy to see, but how to box them can be difficult to determine, especially if an interface has to be developed. The *None tells gmBasic to simply ignore the specification. The *Self type simply refers to the class being defined by the FileFilter code.

The default context is LateCall. Before the compiler produces a late-binding for a weakly-typed host it checks for a CallByName entry and attempts to resolve the LateCall via a boxing operation.

The LateCollection context occurs when nested Item().Item() references occur. For example consider the following situation


 Collection PeriodsCollection;
 PeriodInfo = new Scripting.Dictionary();
 PeriodsCollection.Add(PeriodInfo);
 StartUpString = PeriodCollections(index)("StartUp")
The statement


  <CallByName source="methodname" host="methodname.PeriodsCollection"
              context="LateCollection" boxType="Scripting.Dictionary" />
tells the compiler that the host to be boxed to Scripting.Dictionary is not PeriodsCollection itself, but rather PeriodCollections.Item()".

The LateDefault context singles out subprogram arguments for which unwanted default properties are being propagated. In the line of code


   recordSets.Add("recordset1",myRecordSet)
recordSets is built as a collection, Scripting.IDictionary of recordsets. Its Item is simply typed as variant which means that in Vb6 it expects default property propagation. The default property of a RecordSet is Fields so the above ends up being translated as


   recordSets.Add("recordset1",myRecordSet.Fields)
This context introduces the third type of CallByName refactoring: LateDefault. The actual command goes as follows


   <CallByName source="recordSets" host="Scripting.IDictionary.Add.Item"
               boxType="ADODB.Recordset" context="LateDefault" />
The way the gmBasic compiler works, it only generates a default property during a method call when the parameter is weakly typed as an Object or Variant. In this case that parameter is "Scripting.IDictionary.Add.Item". There are instances when codes pass default properties to this parameter, but in this case the code is passing an object instance of type ADODB.RecordSet itself. The VB6 system makes these decisions at runtime and thus is able to distinguish the two cases. The gmBasic compiler needs to be told that when passing arguments to the Add.Item parameter in the scope of "recordSets" treat that parameter as though it were strongly typed. Its semantically like a Boxing operation, but since passing a stronger type to "Object" requires no Boxing instruction, none is generated.

The Anatomy of CallByName

Both .NET and VB6 support a CallByName method which is used at runtime to get a property, set a property, or invoke a method. In .NET its parameters are as follows:

Parameter Description
--------- ------------
ObjectRef A System.Object that is the host of the call. It is the instance of an object exposing the named property or method
ProcName A string expression containing the name of the property or method on the object that is being referenced.
UseCallType A CallType enumeration entry that specifies the type of procedure being called: Method, Get, or Set
Args() An optional System.Object array, ParamArray, containing the arguments to be passed to the property or method being called.

The VB6 form of this method is almost identical except for the UseCallType parameter which in VB6 is: VbLet, VbGet, or VbMethod. The following three Vb6 CallByNames


   CallByName Text1, "MousePointer", VbLet, vbCrosshair
   Result = CallByName(Text1, "MousePointer", VbGet)
   CallByName Text1, "Move", VbMethod, 4000, 4000

become the following in C#


using VBNET = Microsoft.VisualBasic;
  ...
   VBNET.Interaction.CallByName(Text1,"MousePointer",VBNET.CallType.Set,
                                 new object[]{System.Windows.Forms.Cursors.Cross});
   Result = VBNET.Interaction.CallByName(Text1,"MousePointer",VBNET.CallType.Get,
                                          new object[]{});
   VBNET.Interaction.CallByName(Text1,"Move",VBNET.CallType.Method,
                                new object[]{4000,4000});
In VB6, however, doing explicit CallByName is not required to achieve late-binding and in fact is rarely used. The compiler will generate these calls from equivalent weakly bound references. The following Vb6 statements


 Dim Text2 As Variant
 Text2.MousePointer = vbCrosshair
 Result = Text2.MousePointer
 Text2.Move(4000,4000)
produce the equivalent result in VB6 but in .NET the late binding must still be explicit.


  object Text2 = null;
  VBNET.Interaction.CallByName(Text2,"MousePointer",VBNET.CallType.Set,
                               new object[]{System.Windows.Forms.Cursors.Cross});
  Result = VBNET.Interaction.CallByName(Text2,"MousePointer",VBNET.CallType.Get,null);
  VBNET.Interaction.CallByName(Text2,"Move",VBNET.CallType.Method,
                               new object[]{4000,4000});
The focus of the CallByName statement is to return translations like the above into direct calls by inserting a boxing operation that specifies at compile-time the assumed type of the host symbol.

Recent .NET releases have introduced an explicit type Dynamic which allows code like the VB6 to be written. This type is supported by gmBasic via its FixType refactoring statement. In fact the above could be removed by a simple FixType to TextBox. There are complex situations where these direct solutions are either not workable or inappropriate such as with members of collections where the collection member type is not easily changed. The CallByName refactoring statement uses a boxing operation around the host symbol to avoid having to change any declarations. In this case, the following expansion of the translation command script


   <Compile Project="C:\gmSrc\GMTest\vb6test\VB0005\vb6\VB0005.vbp" >
      <Refactor FileFilter="C:\gmSrc\GMTest\vb6test\VB0005\vb6\VB0005.frm">
         <CallByName source="Command1_Click" Host="Command1_Click.Text2" BoxType="TextBox" />
      </Refactor>
   </Compile>
produces the following translation


   ((System.Windows.Forms.TextBox)(Text2)).Cursor = System.Windows.Forms.Cursors.Cross;
   Result = ((System.Windows.Forms.TextBox)(Text2)).Cursor;
   ((System.Windows.Forms.TextBox)(Text2)).SetBounds(4000,4000,0,0);
In the simple case presented above, the .NET CallByNames will behave in exactly the way they did in VB6. There are even instances where the .NET CallByName translation is the desired one. It is when the ProcName parameter becomes complex that real problems emerge. Consider this translation produced for a VB6 statement


   frm.txtFieldData(i).Text = vbNullString
      where frm is Variant
   VBNET.Interaction.CallByName(frm,"txtFieldData(i).Text",VBNET.CallType.Set,
                                new object[]{VBNET.Constants.vbNullString});
In .NET the ProcName must be a simple identifier. The translation correctly shows the intent of the VB6 and will even build in .NET, but it will throw an exception at execution time.
Table of Contents