Gemini enables one to write native methods to handle situations when a migration cannot be written entirely in gmSL (Great Migrations Scripting Language), e.g., when the standard capabilities of the translation tool do not support some operation needed by the compiler, analyser, author, or auditor. Code to be made available to a translation tool is linked into a dynamic-link-library that is then executed by the tool when certain events occur. These events and the details of their handlers are discussed in this subsection.
Explicit examples are provided to both document the actual events and show the situations in which these events can be used:
The migration of Flp32a30.ocx To Telerik.WinControls.GridView is performed using the standard refactoring and editing facilities available with the tool; however, there are a few issues that cannot be. In this discussion three such events will be described:
To take advantage of these events external refactoring libraries register runtime DLLs with the tool that are to be loaded when that specification is read. For example, the specification
tells the tool that when it encounters a request to load the named OCX, typically in a VBP file, then it should also load the named DLL for exports that satisfy given named events. Thus, the external references event is handled by an export named "AuthorReference". The startup of the analyser is handled by an export named "StartAnalyser" and the transformation event is handled by "Transform". The front-end of a DLL that handles these events would look as follows.
Note that the actual content of these handlers is highly patterned. The migration specialist actually supplies a scripting tool with a simple specification of what events and what components he wishes to work with. That scripting tool then produces the code framework needed with stubs to be filled where needed.
The problem addressed here is that ASP include files are code fragments that reference symbols that are not defined within the file itself. Instead, they are:
Within the default behavior of the tool, the determination of the status of the external symbols is made on the basis of the environment in which the included is first loaded. This is the standard rule for these types of situations where user components are used multiple times. In this instance the rule does not work in some situations. The environments surrounding includes vary wildly. A stronger approach is needed to ensure that all translations for a given include are at least consistent. First all pageslices that are eventually to be combined into a site build are compiled and translated individually to the point where they are well-formed and ideally build.
The introduction of GlobalIncludes has also necessitated a change in the way in which the tool processes included pages. Here are notable changes:
gmNI:DLL_EXPORT int StartPass2(int target,int migNumber)
The event handler StartPass2 is called immediately before the compiler starts its second pass while compiling a project code or a pageslice. The first pass of the compiler has been completed; therefore, the global symbols table is present. No code has been generated and no local variables have been stored in the symbol table. The parameter target is the offset of the project or the main page to be compiled. The parameter migNumber is the sequence number, relative to one, of the load order of the dynamic link library containing the handler. During any given run, this number is unique and can be used to mark components as needed. The number will vary from run to run. The handler is expected to return a one if it performs any actions based on the target component, and a zero it does not. In the case of this particular event all handlers are called regardless of this return value.
The purpose of this handler is to make any changes in the types and other characteristics of any symbols to ensure a clean pass2 compile. In the case of the global includes this involves looking in the registry for special information about the pages. That logic is implemented in a private method GlobalIncludes_StartPass2 in the library, which will not be shown here. The method below shows the actual handler.
It gets the handles to the global code block and select information from the tool in case they are needed and then it executes the local method and returns a one.
AnaUser.AnaUserMigration() CodeReview AnaUser.AnaUserPass1() Initialize
thor.AuthorSupportReference() The first issue involves using the AuthorReference Event. This event occurs when the author is writing the project file immediately before writing the list of external references needed by project. These typically look as follows
The information for this list comes from the name and location attributes of the library commands in the referenced IDFs. The purpose of the call is to give user the opportunity to change how the reference is to be written. Since the event is called only once, using it typically requires two things -- first, declare the location of the reference as "DoNotDeclare" and second, author the desired reference within the AuthorReference code in the DLL.
In the present example the reference to the OCX is being displayed as follows
Instead a series of references like the following is needed
To achieve this the location of the library is migrated to DoNotDeclare and the following event handler code is added to the LpLibMigration dll.
The remainder of the issues associated with this method use the StartAnalyser event and then a series of Transform events. The StartAnalyser event occurs when the analyser first starts up before it performs any other operations. Its purpose is to allow the user to do any initializations that might be needed later. This event handler must always be present when the transform event is to be used. The transformation event is triggered by markers in the library components information vectors that are placed there by this handler. Note that this actual handler is normally written for the user by a script based on a simple list of the names of the components being transformed or referenced. The components themselves are identified as "nameSpace.className.compName". This nameSpace is normally the library name attribute value. The ClassName is the identifier of the actual IDF class that contains the component. It is NOT the coClass identifier that is typically used in SourceCode. Finally, compName is the actual name of the component in the named class in the named namespace.
The transform events occur during the final scan of the code by the analyser. Each reference to each component marked by the StartAnalyser event triggers a Transform event. Transformation is controlled entirely by the event code. It does not expect or require any entries in the IDF entries for the library components being migrated. Most transformations, however, end up referencing operations that are not part of the original external reference. That's really the whole point behind this operation -- introducing new operations into the code to accommodate the new implementation. These additional operations are added to the refactor specification via a migClass command. These are discussed below.
In the unmigrated code the number of columns is set to a value.
In the migrated call the equivalent operation is to clear out the columns and then to add as many as are indicated. This would look as follows:
Using transform usually ends up replacing calls in the unmigrated code with calls to migrated components that did not exist in the original reference. In this case we want to end up performing a Clear operation followed by an operation that adds columns. We define these operations using a migclass which is added to the RefactorLibrary specification.
Note that these specifications include how they are to be authored in the target. The %1d in the above patterns refer to the "hostObject" of the call which is "cmbName" in this case. The %2d in the AddColumns pattern refers to the first argument of the call. This argument is named "rightSide" because it will receive the "rightSide" specification from the property setter.
We now have everything we need to write the transformation code.
First of all note that there are many scripting facilities available to set up these codes. These utilities will end up creating Stubs for the components. The migration expert will have an "Columns" stub already laid out for him into which he can enter the code he wants.
The first step is to ensure that we are looking at a reference that is setting a Property equal to something. These are called "PropertySetters". If this is not a Property Setter the method returns a zero indicating that no change was made. If it was a PropertSetter, then the indexInfo tCodeGroup structure will have the code associated with the operation broken into 3 labeled arguments:
|target||This is the codeblock that actually references the target symbol that triggered the call to this method. The actual reference is usually followed by a MEM.Child operation which ties the target symbol to the host object. When present this operation is included in the codeblock. Typically this codeblock will be deleted when the targetcode is transformed.|
|rightSide||This codeblock specifies the value that is to be assigned to the property.|
|hostObject||This codeblock specifies the user object instance whose property value is being set.|
For example in "employee.pay = dailyRate * daysInPayPeriod" the "target" argument would be the code that specifies the "pay" property; the "rightSide" argument would be the code that specifies the pay calulation; and the "hostObject" argument would be the code that specifies the "employee" object. Remember we are talking about code not about character strings.
The second step asks if the "rightSide" value is a simple integer value. If a NO_VALUE_FOUND is returned than we have a complicated code reference of the sort described in the wrinkle and we abort the migration.
The third step now creates calls to the two derived methods that were defined in the migClass. Note that the names of the arguments are keyed to the names assigned to the arguments of the PropertySetter.
The fourth step completes the migration and replaces the original code with the newly formed migration code. A comparison of the new version of the code shows the result
In the unmigrated code a "Col" property is set and then subsequent column property setting are applied to that column. Thus a code sequence like
establishes a width and a name for the first column. Within the migrated code this column number has to be associated with the column-specific property setting. Thus the migrated code needs to be something like the following:
As in the above we first add the new operation to the LpLibComboBox migclass.
The hostObject identifier will be used to form the name of the particular colIndex and rightSide value will again appear on the rightSide. The transformation code is as follows:
First we make certain that this is a set for the Col property. Second, we need the root offset of the hostObject on the leftSide of the Set, because this offset is needed to form the name of the colIndex. Note that the actual lValue (left Value) includes the target offset as well, so we decrement it by one. If we do not get at least one offset -- meaning a simple local object variable -- we abort the migration. Third we need the code for the new Combo.setCol method to replace the setter. Fourth, we add a declaration of a variable to the front of the method code if that variable is not yet declared. The variable colIndex is a string variable
which defines the template for the name of the variable. If a new declaration is added then the method returns the length in bytes of that declaration. This value is needed and returned to the calling code scanner so that it can reposition itself. If no declaration is added, because one already exists, then defineVariable return zero. Implementing this handler introduces the variable declaration and makes the expected change in the setter,
As in the previous examples the new methods are first added to the migClass.
The actual transformation code is straightforward. Only the code for Width is shown, the others are the same.
First make certain its a property setter and then do the replacement.
AspAuthor.AspAuthor() AspAuthor.LocAuthorPageTop() Author.Vb6AuthorProject()
Whenever the translation tool encounters an XML command in a translation script that it does not recognize it executes the ExecuteCommand() event handler in each loaded runtime DLL that has this handler defined in the order that the DLLs were loaded. If one of these handlers returns a non-zero value, then the tool assumes that the XML command was processed by that DLL and ends its search. If no DLL indicates that it has processed the command, then the tool issues a command not found error.
For this example assume a command "FindDimAsNew" is to be implemented. This actual command is discussed in the section on the DimAsNew Runtime Dll. This discussion is only concerned with the command key. The actual implementation of the command would have the following prototype:
which is called by the actual exported ExecuteCommand() handler with the following prototype:
The command character buffer contains the actual command as entered in the translation script in null-terminated form. All leading and trailing blanks have been removed. In this example it might contain
The nCommand integer specifies the overall size of the command buffer. By convention in the script processing logic all methods use this same command buffer for storing the commands read from the script. Even though the actual memory for the command buffer is allocated in tool memory, it can be freely used in the handler so long as its size is not exceeded.
The implementation of the handler is highly patterned and should always have a form similar to the following:
This handler could be added to any Runtime DLL currently being loaded or could be placed in the main control logic of a separate DLL.
The first step (a) retrieves the setting of the Selection properties. Almost all implementations need to get values of some of these properties, so it is always a good idea to include this operation in all event handlers.
The second step (b) checks for the specified Xml-style command keyword with the '<' character immediately before the command word. If there is already an ExecuteCommand() handler present, the above logic could be added with an else if. Again, the actual acceptance and execution of the command is triggered entirely by its initial command word. If it is matched, the handler return value is set to one and the implementation method is executed. If it is not matched, the return value is set to zero. The zero setting tells the tool to continue its search.