This section contains samples of gmAPI applications.
The topic gmsl.Samples.RDOtoNet presents a detailed discussion of a migration of the external library "MSRDO20.DLL#Microsoft Remote Data Object 2.0" to the .NET class "System.Data.SqlClient". That implementation depended on gmSL COM Event Handlers along with extensive modifications of the standard interface description file MSRDO20.dll.xml. This topic performs the same migration using a C# class msrdo that simply uses the standard IDF. This discussion assumes familiarity with the gmSL discussion of this migration.
Within the gmAPI the migration of external libraries makes heavy use of the CodeGroup class. This class was originally developed for use with gmNI and has no direct equivalent in gmSL.
The first step was to write a simple gmAPI based program called "Trans_RDOtoNet" that produces both the unmigrated and migrated translation of the sample code.
The two methods RDOtoNet_csh() and RDOtoNet_mig() are direct translations of the two gmPL translation scripts RDOtoNet_std.xml and RDOtoNet_mig.xml and produce the same translations as their corresponding gmPL script. As with the gmPL versions, the only substantive difference between the two scripts is the Target directory. The standard script uses the standard IDF for MSRDO20; while the migrated script uses the migrated IDF and associated gmSL code. In general, there is no need or advantage to completely abandon using gmSL when doing gmAPI based migrations. Large scale migrations will typically include both approaches. That said this sample will do the entire migration using the gmAPI
The first step in the gmSL migration was to copy the standard MSRDO20.DLL.XML IDF and to add migration names, using gmPL, to reflect any simple name changes that needed to be done. The approach here will be to copy method RDOtoNet_csh() as a new method RDOtoNet_api() and a new class msrdo that will perform the detailed migrations of the standard IDF. Here is the initial version of the new method RDOtoNet_api()
For housekeeping purposes the names of the storage area and the output file are changed. Note first that it uses the standard IDF for MSRDO20.DLL. Notice second that the changes begin with the "Execute.Reference" call which pre loads that standard IDF. Since the added migration names include code patterns they must be made before the compiler runs; therefore, the IDF must be loaded explicitly. Third, the two calls to the msrdo class then make the changes. The initial code for this class is as follows.
The first part of every gmAPI based migration code consists of a list of the components to be explicitly referenced in the code along with their fully qualified identifiers. This list includes those components whose names and/or surface patterns are to be changed and any additional components whose references are to be transformed. The gmslLibrary method Analyser.StartAnalyser() then finds and stores the root offsets of these components and logs warning messages if any are not found. Using these root offsets the method AddMigrationNames() then makes the initial changes. Note that in addition to adding names and patterns to existing symbols in the IDF the changes to MSRDO20.dll.XML for the gmSL migrations also contain a new "DotNet" migclass.
The purpose of this addition was to add new surface patterns that could then be referenced by the migration code. In this approach these patterns can simply be added directly using the Symbol.StorePattern method. With this machinery in place, the api translations are now as follows.
This matchs the translation as of this step in the gmSL migration. To produce this new translation the method Translate.Main() is revised to produce three translations and three virtual binary information files.
The goal of the remaining effort here is to produce an "api" bundle that matchs the gmSL produced "mig" bundle which is also matched by the current gmAPI translation RDOToNet_mig.bnd.
The first difference between the current translation and the target translation is in the method connectDB.
The character string, which consists of a series of "name=value;" pairs has been changed to remove the Driver pair and the DSN pair.
The first question that has to be asked is "How do we determine which character strings need to be changed?". In an editing context, perhaps all strings could be searched for these pairs and they could be universally removed. This would almost certainly work in this case, but in general changes need to be contingent on their use. The string above needs to be changed because in this later statement
it is being assigned to a connection string. Connection strings in the target environment do not have these two attributes. A search needs to be made of assignments to the ConnectionString property, and the strings used in these assignments need to be edited, if possible and necessary. It should be noted that this property has already been manipulated in the subtoptic "Adding a few migration names" where its target name was set to "ConnectionString". In that discussion its fully qualified source name was "RDO._rdoConnection.Connect" and its internal identifier within the msrdo class is "RDO_rdoConnection_Connect".
Migration is not a process that works with source code or target code. It works with the intermediate code produced by the compiler. After the compiler has completed and after the analyser has finished the standard migrations built into the tool, references in the intermediate code to components in external libraries can be scanned looking for those that have user supplied code registered for them. The purpose of the user code is to change the intermediate code so that it performs the target operation as opposed to its original source operation.
Once a component that requires migration has been identified, the first step is to locate references to it in the intermediate code. Going though this process here manually, first use the tool gmMetrics to produce a full audit report of the file "RDOToNet_api.vbi" produced above. The report itself is in a text file called "RDOToNet_api.txt". As a first step, search the "Audit of Symbol tree in RDOToNet_api.vbi" report for the fully qualified source name "RDO._rdoConnection.Connect".
The important information here is the value "56217" in the "Address" column which contains the unique root offset of this component. The remainder of the audit report can now be searched for this value. Doing that uncovers a reference in the table
to the intermediate code for the conectionString assignment statement that we are interested in.
Starting the actual migration process, then, will involve writing a method in the msrdo class that will be notified of all code references to the Lib_PropertyRDO._rdoConnection.Connect. The purpose of that method will be to locate any strings being assigned to that property and to edit them to remove any unwanted attributes.
The process of finding references to components so that those references can be migrated is exactly like the manual process described above. First, a list of the fully qualified names of the components is created and the root offsets of these components are recorded. This step was shown in the subtopic "Adding a Few Migration Names" and is performed via the statement "msrdo.StartAnalyser(99)" in the method RDOtoNet_api().
The second step is performed via the statement "msrdo.CodeScan(99)" which scans the code for all the references to the root offsets derived via "StartAnalyser()" and then calls a separate method for each. Here is the source as added to the class msrdo.
The code scan itself is performed via the method Analyser.CodeScan(). This method will call the method RDO_Transform() each time it encounters a component that is identified via the migNumber for msrdo which is 99 in this sample. The method RDO_Transform() will in turn extract the component identifier number and will call the appropriate method for each. So far the only method implemented is the msrdo_Connect() method called each time the RDO_rdoConnection_Connect component is referenced. At this point that method simply logs a message describing the reference.
All transform methods have four parameters. The parameter subRoot is the root of the source code component that references the component being transformed. The parameter target is the root offset of the component referenced. The parameter iRefer is the code location of the actual reference to the transform component. The parameter qeStack is a vector used internally to keep track of the starts of the code for the nested quantity expressions surrounding the reference to the target component. Its initial entry specifies the nesting level and its following members contain the starting offsets at each level. Its exact values will vary by the type of reference. Running the program now generates the following message.
The actual message produced by the transform method, confirms what was obtained manually before which is repeated here.
The method connectDB contains a reference to RDO._rdoConnection.Connect whose unique root offset is 56217 at code offset 79 and that a good starting point for the overall reference is at offset 67 which in this case is the offset of the reference to the variable dbs whose content will eventually be evaluated and modified. Finally, the offset of the fully qualified reference to the property is at offset 74 which is the reference to the variable conn.
The actual code used to ultimately change the content of the dbs connection string works through references to the RDO._rdoConnection.Connect component. The simple version of the method msrdo_Connect which simply logs a message is replaced by a method which does the migration. It is as follows.
The first thing to notice is the use of the gmslLibrary.CodeGroup class. This class generalizes the types of references and migrations that are performed when migrating external libraries. Particular migrations can then be done more easily. Step 1 then is to create an CodeGroup instance which can be manipulated. This particular migration deals with a value being set for the Connect property; therefore, if this particular reference is not a set to a property value then it is not to be migrated here. Step 2 then checks this. In addition to verifying that the reference is a property value set the method isPropertySetter also establishes the various code segments associated with the reference. In this case we are interested in the righSide segment which contains the code for the value being assigned. Step 3 verifies that the rightSide segment has a string type. If so, step 4 attempts to actually obtain that value. This may or may not be possible depending on the actual code. In this case though the actual assignment to the property is via a local variable, the logic in the CodeGroup class is able to find the string asigned to that variable; therefore, the actual transformation can proceed. The step 5 code simply changes the string value to remove the two unwanted name, value pairs. The final step 6 is to replace the old string expression with the revised string. Note that this replacement may well shift code that precedes the referencing code. The calling method that is scanning for transform references needs to be told that this has happened. A non-zero return value tells the scanner that the method has made a change in the code and that scanning should resume at the indicated code location.
Running this new code does now produce the desired change as the following file comparison shows.
The next difference between the unmigrated and migrated code is in execQuery in the string assigned to the variable SQL.
As with the previous string change requirement the fact that this string should change is determined from the fact that the variable SQL is used as the second argument to the method RDO._rdoConnection.CreateQuery.
Though the reference pattern is different and the actual editing is different, the logic of the migration method is about the same as the logic of the msrdo_Connect method. The name of the method is now constructed to refer to CreateQuery, the parameters are the same four.
The first step is to make certain that this reference is a valid call to the method. The second step examines the "SqlString" code segment associated with the instance. For method calls the code segment names are derived from the definition of the method in its interface description file.
Thus "SqlString" is the name of the code segment associated with the second parameter of the method call. Except for the name of the code segment the calls to the methods isString, stringValue, and stringReplace are the same as they were in the msrdo_Connect migration method.
Running this code to this point causes the desired change in the translation as the following file comparison shows.
In addition to having to change the content of the query string, the actual call to the method needs to be changed into a combined method call followed by a propery assignment. The actual difference is
The easiest way to acheive these types of changes is to invent a new surface pattern that reflects the new desired target syntax. This added declaration was introduced as part of the AddMigrationNames method described earlier.
Note that this pattern combines the code associated with the call to the method CreateCommand with its assignment to SP. The actual code here is this
The CUF.Args2 operation marks the end of the code group associated with the isMethodCall instance. So the migration needs to know whether the instance ends in the operation sequence CUF,REF,ARG,CMD.Set and if so then that ending should instead be a reference to the surface pattern above. Operation sequences like the above are called "code patterns" and are processed via the CodePattern class and can be accessed via the CodeGroup instance. Here is the additional code needed.
This code says exactly what is needed. If the instance ends in the specified code pattern then replace that ending with a reference to the CreateCommand surface pattern. Here is the final code.
Note that the statement that uses the new surface pattern is highlighted. The comparison log shows that the code achieved the desired result.
The next difference between the unmigrated and migrated code is as follows. This particular difference occurs twice.
The compiler processes this by generating a generic COL.Item operation that must be replaced by a pattern string.
The code produced by the tool before this migration is applied is as follows.
This method msrdo_Parameters performs the transformation.
First it verifies that the reference to RDO.rdoParameters is a property getter type operation. Second, it verifies that the reference is followed by a constant integer subscript associated with the property using the COL.Item operation. If so, it replaces that operation with a reference to the Parameters surface pattern. As the following change log shows, both instances of the rdoParameters where changed.
An important difference in the migrated code has to do with the SqlDataReader variable Results. In the unmigrated version, it is declared as a method level local variable and is then opened and closed in the code.
In the migrated version, its scope is limited, as it is declared and opened in a using statement and then closed by closing the bracketed loop;
The two methods that transform the open and the close are shown here. They proceed in much the same manner as the ones already presented.
The only new CodeGroup method introduced is the getSetTarget method which retrieves the root offset of the left-hand-side of the set which is the variable Results. Setting the DeadCode property of this variable to true blocks its explicit declaration.
The next difference is in the while loop that reads the records from the result set.
In the current unmigrated code the while loop checks for an end-of-file while the target code performs the actual read. Note that using the types of techniques used earlier, the approach already changed the migName of the EOF property to Read() in the method AddMigrationNames.
Then the migration method for the property can simply check for the NOT operation and remove it. This is what the actual reference code looks like.
The migration method then merely checks for the pattern and removes the NOT if present. The code is straight forward.
Since the surface pattern offset sent to the method replaceEnding is zero, the call results in the simple removal of the ending code. Checking the change log shows that the combination of the new migName and the removal of the NOT acheived the correct result.
Migrations that combine noncontingent renaming with contingent code modification are referred to as "shallow" migrations. The technology used by the tool is derived from the field of transformational grammar. The meaning of sentences is referred to as "deep structure", and the representation of the sentence as uttered is referred to as "surface structure". Rules that mix these two levels are called "shallow" and should generally be avoided. In our sample code, the only reference to the "EOF" property is in that while clause where the renaming to "Read()" is valid. But in other contexts, the transform would fail to apply, but the "EOF" would still be changed "Read()" -- certainly causing bad code.
In places such as this, shallow transforms are fine, but beware of them in larger scale migrations where they can introduce problems. A more complex approach would introduce a new surface pattern Read and then do a contingent replacement.
The next difference is in the while loop that reads the records from the result set.
This difference involves the references to two properties rdColumns and to Value both of which can simply be removed. The actual references can be seen in the code audit.
Except for the names of the methods they are identical.
The calls to argReplace are the same as those to replaceEnding except that the code segment associated with the named argument of the instance is replaced. The zero surface pattern value simply performs a deletion of the named code segment. The change log shows that these changes produced the desired result.
The final difference between the two translations is that the MoveNext call is no longer needed. The migrated code already does the read in the while loop. The transform method can simply comment out the unwanted statement.
his demonstration migration has now been completed,