Login   /   Register

Function Pointers in VB Script (revised)

Rate this article
     2 votes, average: 4.5 out of 52 votes, average: 4.5 out of 52 votes, average: 4.5 out of 52 votes, average: 4.5 out of 52 votes, average: 4.5 out of 5
Loading ... Loading ...
December 9th, 2008 by Meir Bar-Tal

Abstract

Function pointers are used in C and C++ to enable callbacks and as a result, more generic and parsimonious coding. This article shows how to implement function pointers in VB Script using the Scripting.Dictionary object and the Command Wrapper design pattern. It is also shown how to implement “constructor” functions, callbacks and event handlers.

 

Keywords: function pointer, callback, delegate, scripting dictionary, hash table, design pattern, command wrapper, event handler.

 

Introduction

In the following sections I shall explain several basic concepts that are essential to understand the rationale behind the suggested method. The list might be non-exhaustive and the reader is hereby encouraged to recur to complementary sources to fill-in any eventual knowledge gaps.

 

Function Pointers

A function pointer is a variable that stores the memory address of a block of code that is programmed to fulfill a specific function. Function pointers are useful to avoid complex switch case structures; instead, they support direct access in run-time to previously loaded functions or class methods. This enables the construction of callback functions. A callback is, in essence, executable code that is passed as argument to a function. This enables more generic coding, by having lower-level modules calling higher-level functions or subroutines.

Function pointers are supported by programming languages like C and C++. A recent good introductory text on the subject with implementation examples is Lars Haendel (2005)

This article shows how to implement function pointers in VB script.

 

Design Patterns

Design patterns are well established generic design techniques whose aim is to make software design and the derived code more parsimonious, scalable, and reusable, as well as more easily maintainable. This article will delve only into the command wrapper design pattern, which is important for its purpose. For a more extensive explanation of design patterns, the reader is encouraged to read the literature in the field (e.g., Gamma, Helm, Johnson, & Vlissides, 1997).

The command-wrapper design pattern enables to load blocks of code dynamically to memory as well as other uses as will be reviewed below. The singleton design pattern is used to restrict instantiation of a class to one object. This is useful when there is no need for more than one object within a system (e.g., a reporter/logger object). For example, the factory design pattern usually implements the singleton design pattern to have a single interface from which to retrieve references to a specific class object or to a variety of class objects.

 

The Command Wrapper Design Pattern

The command wrapper design pattern is a technique that implements a function as a class, usually with a single public method. During run-time, it is possible to instantiate the class and thus to get a reference (or pointer) to the object. This way, the code embodied in such methods can be dynamically loaded, a feature that can be of great value in systems that are poor in resources. This is especially true when the to-be called function is rarely used. After creating the object, it is possible to execute its method according to need. For example, let us assume a function that adds numbers (note: this example is, of course, trivial. It is done just to illustrate the point.)

   1: Public Function Sum(ByVal arrNumbers)
   2:     Dim ix
   3:     
   4:     'Check if an array was passed. If so, there’s nothing to do – exit function
   5:     If (Not IsArray(arrNumbers)) Then 
   6:         'Add your error handling code here
   7:         Exit Function
   8:     End If
   9:     
  10:     Sum = 0
  11:     For ix = LBound(arrNumbers) To UBound(arrNumbers)
  12:         If (IsNumeric(arrNumbers(ix))) Then
  13:             Sum = Sum + arrNumbers(ix)
  14:         Else
  15:             'Add your error handling code here
  16:         End If
  17:     Next
  18: End Function
  19:  
  20: 'Test the function and display result returned by the Sum function
  21: MsgBox Sum(Array(23, 56, 78, 95, 114)), vbOKOnly, "Result from simple function" 
  22:  
  23:  
  24: 'Now, let us convert this function into an object using the Command Wrapper design pattern:
  25: Class Sum
  26:     Private m_arrVarNumbers
  27:     Private m_varResult
  28:     
  29:     Private Sub Class_Initialize()
  30:         'Initialize the Numbers member as an empty array
  31:         ReDim m_arrVarNumbers(-1)
  32:     End Sub
  33:     
  34:     Public Function Init(ByVal arrVarNumbers)
  35:         Numbers = arrVarNumbers
  36:     End Function
  37:     
  38:     Public Default Function Exec()
  39:         Dim ix, arrNumbers
  40:         
  41:         'Check if an array was passed. If so, there’s nothing to do – exit function
  42:         If (Not IsArray(Numbers)) Then
  43:             'Add your error handling code here
  44:             Exec = "Invalid data type was supplied to perform the operation."
  45:             Exit Function
  46:         ElseIf (UBound(Numbers) - LBound(Numbers) + 1 <= 1) Then
  47:                 'Array is empty or has single item - Add your error handling code here
  48:                 Exec = "Not enough data was supplied to perform the operation."
  49:                 Exit Function            
  50:         Else
  51:             arrNumbers = Numbers
  52:         End If
  53:         
  54:         Result = 0
  55:         For ix = LBound(arrNumbers) To UBound(arrNumbers)
  56:             If (IsNumeric(arrNumbers(ix))) Then
  57:                 Result = Result + arrNumbers(ix)
  58:             Else
  59:                 'Add your error handling code here
  60:             End If
  61:         Next
  62:         Exec = Result
  63:     End Function
  64:     
  65:     Public Property Get Numbers()
  66:         Numbers = m_arrVarNumbers
  67:     End Property
  68:     
  69:     Private Property Let Numbers(ByVal arrVarNumbers)
  70:         m_arrVarNumbers = arrVarNumbers
  71:     End Property
  72:     
  73:     Public Property Get Result()
  74:         Result = m_varResult
  75:     End Property
  76:     
  77:     Private Property Let Result(ByVal varResult)
  78:         m_varResult = varResult
  79:     End Property
  80: End Class
  81:  
  82: 'This function behaves as a constructor and returns an initialized instance of the class
  83: Public Function GetSum(ByVal arrNumbers)
  84:     Set GetSum = New Sum
  85:     GetSum.Init(arrNumbers)
  86: End Function
  87:  
  88: 'Test the class
  89: Dim objSum, arrNumbers
  90:  
  91: 'Assign an array of numbers
  92: arrNumbers = Array(23, 56, 78, 95, 114)
  93:  
  94: 'Get an instance of class Sum and initialize it with numbers array
  95: Set objSum = GetSum(arrNumbers)
  96:  
  97: 'Execute Sum (Exec method)
  98: objSum.Exec() 
  99:  
 100: 'Display result stored in Result property
 101: MsgBox objSum.Result, vbOKOnly, "Result from Result Property"
 102:  
 103: 'Alternatively, display the result returned by the Exec method:
 104: MsgBox GetSum(Array(23, 56, 78, 95, 114)).Exec, vbOKOnly, "Result from Exec method" 

 

The Scripting Dictionary

In VB Script, the scripting dictionary is an object that stores key-item pairs using a hashing algorithm. The items can be accessed using their corresponding keys. Basically, the dictionary is useful to store variant type, non-structured data, such as the catalog number of an item in an inventory list, or the authors of a list of books, and to retrieve the data using the unique keys supplied, as in the following example:

   1: Dim dicBooksAuthors
   2:  
   3: 'Create an instance of the scripting dictionary class
   4: Set dicBookAuthors = CreateObject("Scripting.Dictionary")
   5:  
   6: With dicBookAuthors
   7:     'Add some books (keys) and authors (items)
   8:     .Add "The Selfish Gene", "Richard Dawkins"
   9:     .Add "The Mismeasure of Man", "Stephen J. Gould"
  10:     .Add "The Da Vinci Code", "Dan Brown"
  11:     
  12:     'Answer the question: who wrote "The Selfish Gene"?
  13:     strBook = "The Selfish Gene"
  14:     MsgBox .item(strBook), vbOKOnly+vbInformation, "Author query result for book: " & strBook
  15: End With

The example above demonstrates the power of a dictionary as a data storage and data retrieval device. For instance, it eliminates the need for item search functions (direct access) and avoids duplicate key creation.

However, the dictionary can be much more than just a data storage and retrieval device. Thanks to its capacity to store variant data types, it is actually possible to store references to objects of different types. For instance, it is possible to store complex data structures by using nested dictionaries (this issue, however, is out of the scope of the current paper). In the next chapter, we shall delve into a method to exploit this feature of the scripting dictionary object to implement function pointers in VBScript.

 

Implementing Function Pointers in VB Script

Earlier in this article, we have shown that it is possible to encapsulate any function as a class by using the command wrapper design pattern. In addition, it has been indicated that the VBScript Scripting Dictionary object is equipped with the capacity of storing variant data types, including references to objects. In what follows a method will be described to combine these two features into a powerful design and programming technique.

Recall that a pointer is a variable that stores the memory address of a function or instantiated class object. Following the example given above in the Command Wrapper section, we shall now show how it is possible to build function pointers using a Scripting Dictionary. Here below, we shall demonstrate the case of the four basic arithmetic operations.

 

The Addition Operation

   1: Class Sum
   2:     Private m_arrVarNumbers
   3:     Private m_varResult
   4:     
   5:     Private Sub Class_Initialize()    
   6:         'Initialize the Numbers member as an empty array
   7:         ReDim m_arrVarNumbers(-1)
   8:     End Sub
   9:     
  10:     Public Function Init(ByVal arrVarNumbers)
  11:         Numbers = arrVarNumbers
  12:     End Function
  13:     
  14:     Public Default Function Exec()
  15:         Dim ix, arrNumbers
  16:         
  17:         If (Not IsArray(Numbers)) Then 'Not an array, so nothing to do – exit function
  18:             'Add your error handling code here
  19:             Exec = "Invalid data type was supplied to perform the operation."
  20:             Exit Function
  21:         ElseIf (UBound(Numbers) - LBound(Numbers) + 1 <= 1) Then
  22:             'Array is empty or has single item - Add your error handling code here
  23:             Exec = "Not enough data was supplied to perform the operation."
  24:             Exit Function
  25:         Else
  26:             arrNumbers = Numbers
  27:         End If
  28:     
  29:         Result = 0
  30:         For ix = LBound(arrNumbers) To UBound(arrNumbers)
  31:             If (IsNumeric(arrNumbers(ix))) Then
  32:                 Result = Result + arrNumbers(ix)
  33:             Else
  34:                 'Add your error handling code here
  35:             End If
  36:         Next
  37:         Exec = Result
  38:     End Function
  39:     
  40:     Public Property Get Numbers()
  41:         Numbers = m_arrVarNumbers
  42:     End Property
  43:     
  44:     Private Property Let Numbers(ByVal arrVarNumbers)
  45:         m_arrVarNumbers = arrVarNumbers
  46:     End Property
  47:     
  48:     Public Property Get Result()
  49:         Result = m_varResult
  50:     End Property
  51:     
  52:     Private Property Let Result(ByVal varResult)
  53:         m_varResult = varResult
  54:     End Property
  55: End Class
  56:  
  57: 'This function behaves as a constructor and returns an initialized instance of the class
  58: Public Function GetSum(ByVal arrNumbers)
  59:     Set GetSum = New Sum
  60:     GetSum.Init(arrNumbers)
  61: End Function

 

The Subtraction Operation

   1: Class Subtract
   2:     Private m_arrVarNumbers
   3:     Private m_varResult
   4:     
   5:     Private Sub Class_Initialize()
   6:         'Initialize the Numbers member as an empty array
   7:         ReDim m_arrVarNumbers(-1)
   8:     End Sub
   9:     
  10:     Public Function Init(ByVal arrVarNumbers)
  11:         Numbers = arrVarNumbers
  12:     End Function
  13:     
  14:     Public Default Function Exec()
  15:         Dim ix, arrNumbers
  16:         
  17:         If (Not IsArray(Numbers)) Then 'Not an array, so nothing to do – exit function
  18:             'Add your error handling code here
  19:             Exec = "Invalid data type was supplied to perform the operation."
  20:             Exit Function
  21:         ElseIf (UBound(Numbers) - LBound(Numbers) + 1 <= 1) Then
  22:             'Array is empty or has single item - Add your error handling code here
  23:             Exec = "Not enough data was supplied to perform the operation."
  24:             Exit Function
  25:         Else
  26:             arrNumbers = Numbers
  27:         End If
  28:          
  29:         Result = arrNumbers(LBound(arrNumbers))- arrNumbers(LBound(arrNumbers)+1)
  30:         For ix = LBound(arrNumbers)+2 To UBound(arrNumbers)
  31:             If (IsNumeric(arrNumbers(ix))) Then
  32:                 Result = Result - arrNumbers(ix)
  33:             Else
  34:                 'Add your error handling code here
  35:             End If
  36:         Next
  37:         Exec = Result
  38:     End Function
  39:     
  40:     Public Property Get Numbers()
  41:         Numbers = m_arrVarNumbers
  42:     End Property
  43:     
  44:     Private Property Let Numbers(ByVal arrVarNumbers)
  45:         m_arrVarNumbers = arrVarNumbers
  46:     End Property
  47:     
  48:     Public Property Get Result()
  49:         Result = m_varResult
  50:     End Property
  51:     
  52:     Private Property Let Result(ByVal varResult)
  53:         m_varResult = varResult
  54:     End Property
  55: End Class
  56:  
  57: 'This function behaves as a constructor and returns an initialized instance of the class
  58: Public Function GetSubtract(ByVal arrNumbers)
  59:     Set GetSubtract = New Subtract
  60:     GetSubtract.Init(arrNumbers)
  61: End Function

 

The Multiplication Operation

   1: Class Multiply
   2:     Private m_arrVarNumbers
   3:     Private m_varResult
   4:  
   5:     Private Sub Class_Initialize()
   6:         'Initialize the Numbers member as an empty array
   7:         ReDim m_arrVarNumbers(-1)
   8:     End Sub
   9:     
  10:     Public Function Init(ByVal arrVarNumbers)
  11:         Numbers = arrVarNumbers
  12:     End Function
  13:     
  14:     Public Default Function Exec()
  15:         Dim ix, arrNumbers
  16:         
  17:         If (Not IsArray(Numbers)) Then 'Not an array, so nothing to do – exit function
  18:             'Add your error handling code here
  19:             Exec = "Invalid data type was supplied to perform the operation."
  20:             Exit Function
  21:         ElseIf (UBound(Numbers) - LBound(Numbers) + 1 <= 1) Then
  22:             'Array is empty or has single item - Add your error handling code here
  23:             Exec = "Not enough data was supplied to perform the operation."
  24:             Exit Function
  25:         Else
  26:             arrNumbers = Numbers
  27:         End If 
  28:         
  29:         Result = arrNumbers(LBound(arrNumbers)) * arrNumbers(LBound(arrNumbers)+1)
  30:         For ix = LBound(arrNumbers)+2 To UBound(arrNumbers)
  31:             If (IsNumeric(arrNumbers(ix))) Then
  32:                 Result = Result * arrNumbers(ix)
  33:             Else
  34:                 'Add your error handling code here
  35:             End If
  36:         Next
  37:         Exec = Result
  38:     End Function
  39:     
  40:     Public Property Get Numbers()
  41:         Numbers = m_arrVarNumbers
  42:     End Property
  43:     
  44:     Private Property Let Numbers(ByVal arrVarNumbers)
  45:         m_arrVarNumbers = arrVarNumbers
  46:     End Property
  47:     
  48:     Public Property Get Result()
  49:         Result = m_varResult
  50:     End Property
  51:     
  52:     Private Property Let Result(ByVal varResult)
  53:         m_varResult = varResult
  54:     End Property
  55: End Class
  56:  
  57: 'This function behaves as a constructor and returns an initialized instance of the class
  58: Public Function GetMultiply(ByVal arrNumbers)
  59:     Set GetMultiply = New Multiply
  60:     GetMultiply.Init(arrNumbers)
  61: End Function

 

The Division Operation

   1: Class Divide
   2:     Private m_arrVarNumbers
   3:     Private m_varResult
   4:     
   5:     Private Sub Class_Initialize()
   6:         'Initialize the Numbers member as an empty array
   7:         ReDim m_arrVarNumbers(-1)
   8:     End Sub
   9:     
  10:     Public Function Init(ByVal arrVarNumbers)
  11:         Numbers = arrVarNumbers
  12:     End Function
  13:     
  14:     Public Default Function Exec()
  15:         Dim ix, arrNumbers
  16:         
  17:         If (Not IsArray(Numbers)) Then 'Not an array, so nothing to do – exit function
  18:             'Add your error handling code here
  19:             Exec = "Invalid data type was supplied to perform the operation."
  20:             Exit Function
  21:         ElseIf (UBound(Numbers) - LBound(Numbers) + 1 <= 1) Then
  22:             'Array is empty or has single item - Add your error handling code here
  23:             Exec = "Not enough data was supplied to perform the operation."
  24:             Exit Function
  25:         Else
  26:             arrNumbers = Numbers
  27:         End If
  28:         
  29:         If (IsNumeric(arrNumbers(LBound(arrNumbers))) And IsNumeric(arrNumbers(LBound(arrNumbers)+1)) And (arrNumbers(LBound(arrNumbers)+1) <> 0)) Then
  30:             Result = arrNumbers(LBound(arrNumbers)) / arrNumbers(LBound(arrNumbers)+1)
  31:         Else
  32:             'Add your error handling code here
  33:             Exec = "Invalid data was supplied to perform the operation."
  34:             Exit Function
  35:         End If
  36:         
  37:         For ix = LBound(arrNumbers)+2 To UBound(arrNumbers)
  38:             If (IsNumeric(arrNumbers(ix)) And (arrNumbers(ix) <> 0)) Then
  39:                 Result = Result / arrNumbers(ix)
  40:             Else
  41:                 'Add your error handling code here
  42:             End If
  43:         Next
  44:         Exec = Result
  45:     End Function
  46:     
  47:     Public Property Get Numbers()
  48:         Numbers = m_arrVarNumbers
  49:     End Property
  50:     
  51:     Private Property Let Numbers(ByVal arrVarNumbers)
  52:         m_arrVarNumbers = arrVarNumbers
  53:     End Property
  54:     
  55:     Public Property Get Result()
  56:         Result = m_varResult
  57:     End Property
  58:     
  59:     Private Property Let Result(ByVal varResult)
  60:         m_varResult = varResult
  61:     End Property
  62: End Class
  63:  
  64: 'This function behaves as a constructor and returns an initialized instance of the class
  65: Public Function GetDivide(ByVal arrNumbers)
  66:     Set GetDivide = New Divide
  67:     GetDivide.Init(arrNumbers)
  68: End Function

 

Using the Function Pointers to Perform the basic Arithmetic Operations

   1: Dim dicFunctionHandler
   2:  
   3: 'Create an instance of the scripting dictionary class
   4: Set dicFunctionHandler = CreateObject("Scripting.Dictionary")
   5:  
   6: 'Load some functions
   7: With dicFunctionHandler
   8:     .Add "+", New Sum
   9:     .Add "-", New Subtract
  10:     .Add "*", New Multiply
  11:     .Add "/", New Divide
  12: End With
  13:  
  14: 'Execute the functions using the Function Handler
  15: With dicFunctionHandler
  16:     With .item("+")
  17:         Call .Init(Array(23, 56, 78, 95, 114))
  18:         MsgBox .Exec(), vbOKOnly+vbInformation, "Result (+)" 'Display result returned by the Exec method
  19:     End With
  20:     With .item("-")
  21:         Call .Init(Array(117, 23))
  22:         MsgBox .Exec(), vbOKOnly+vbInformation, "Result (-)" 'Display result returned by the Exec method
  23:     End With
  24:     With .item("*")
  25:             Call .Init(Array(7, 5))
  26:             MsgBox .Exec(), vbOKOnly+vbInformation, "Result (*)" 'Display result returned by the Exec method
  27:     End With
  28:     With .item("/")
  29:         Call .Init(Array(84, 12))
  30:         MsgBox .Exec(), vbOKOnly+vbInformation, "Result (/)" 'Display result returned by the Exec method
  31:     End With
  32: End With
  33:  
  34: 'Or, using the "constructors":
  35: 'First, load some functions
  36: With dicFunctionHandler
  37:     .RemoveAll
  38:     .Add "+", GetSum(Array(23, 56, 78, 95, 114))
  39:     .Add "-", GetSubtract(Array(117, 23))
  40:     .Add "*", GetMultiply(Array(7, 5))
  41:     .Add "/", GetDivide(Array(84, 12))
  42: End With
  43:  
  44: 'Second, execute the functions using the Function Handler
  45: With dicFunctionHandler
  46:     MsgBox .item("+").Exec(), vbOKOnly+vbInformation, "Result (+)" 'Display result returned by the Exec method
  47:     MsgBox .item("-").Exec(), vbOKOnly+vbInformation, "Result (-)" 'Display result returned by the Exec method
  48:     MsgBox .item("*").Exec(), vbOKOnly+vbInformation, "Result (*)" 'Display result returned by the Exec method 
  49:     MsgBox .item("/").Exec(), vbOKOnly+vbInformation, "Result (/)" 'Display result returned by the Exec method
  50: End With

In the above example we have shown how to:

  1. Implement the Command Wrapper design pattern in VBScript;

  2. Implement a “constructor” for a class in VBScript;

  3. Implement a function handler using a scripting dictionary;

  4. Instantiate such custom classes and load them to a scripting dictionary;

  5. Call the loaded function via the dictionary key and retrieve the result.

This is the method suggested in this paper to implement a function pointer in VBScript.

There is also another possible way to call a function implemented with this method, as follows:

   1: 'Execute the functions using the Function Handler
   2: MsgBox dicFunctionHandler(“+”).Exec(), vbOKOnly+vbInformation, "Result (+)"
   3: MsgBox dicFunctionHandler(“-”).Exec(), vbOKOnly+vbInformation, "Result (-)"
   4: MsgBox dicFunctionHandler(“*”).Exec(), vbOKOnly+vbInformation, "Result (*)"
   5: MsgBox dicFunctionHandler(“/”).Exec(), vbOKOnly+vbInformation, "Result (/)"

and this is because the item property is the scripting dictionary’s default property.

In a similar fashion, it is possible to define the Exec methods of the above mentioned classes as Default (by declaring it: Public Default Function) and then the code above can be further reduced to:

   1: 'Execute the functions using the Function Handler
   2: MsgBox dicFunctionHandler(“+”), vbOKOnly+vbInformation, "Result (+)"
   3: MsgBox dicFunctionHandler(“-”), vbOKOnly+vbInformation, "Result (-)"
   4: MsgBox dicFunctionHandler(“*”), vbOKOnly+vbInformation, "Result (*)"
   5: MsgBox dicFunctionHandler(“/”), vbOKOnly+vbInformation, "Result (/)"
   6:  

The readers are encouraged to try to execute the sample code shown in this paper, as well as to try the method to implement their own functionality.

 

Performance of Function Pointers

A small case study comparing the performance of regular function calls with that of function pointers shows that the more a function is used, the more worthwhile it is to use function pointers.

Number of Calls Single 10 20 50 100
Regular calls

0.0943

0.5630

1.0635

2.5806

5.0674

Function Pointers

0.1561

0.5308

0.9212

2.0922

4.1219

 

Discussion

We have seen so far how to implement the Command Wrapper design pattern in VBScript and a “constructor” for a class in VBScript, as well as a function handler using a scripting dictionary. We have also shown how to instantiate such custom classes and load them to a scripting dictionary, together with different ways to call the loaded function via the dictionary key and to retrieve the result. We have also indicated that this method is, in fact, equivalent to the implementation of a function pointer in C or C++.

The general uses and benefits of function pointers are explained elsewhere (e.g., Lars Haendel, 2005), and hence they will not be covered here. In what remains I shall attempt to convey in which cases the implementation of this design pattern in VBScript in general, and with Quicktest Pro (QTP) in particular, might be of benefit.

First, the method presented in this paper should be of great value when the basic hardware configuration is poor, i.e., when the system is low in RAM, by means of dynamic loading of code blocks. Recall that common QTP usage requires the automation developer to add every single function library to the test resources.

Second, by implementing a function handler as illustrated above, it would be possible to build a generic controller component that would enable execution of real keyword-driven scripts. With such a component it would be possible, for example, to define the flow of the different code blocks in an external file, such as an XML file.

Third, the method can be used to emulate callbacks, which is a central feature of function pointers. This can be easily done by passing the function handler entry (dictionary item) to another function.

Fourth, the method can be used to emulate event handling. This can be achieved by returning a string with the name of the function to be called. Please notice that this technique would yield a highly parsimonious coding style, for it makes the need for decision structures to analyze the return code of a function obsolete. An example for this can be found in Appendix 2.

 

Conclusion

This paper attempted to demonstrate how to implement function pointers in VBScript, and pinpointed the possible uses and advantages of the method in general and particularly for QTP. It is concluded that the technique can help developers to achieve more efficient and generic design and code, and better run-time resources management. Future forthcoming articles will further expand on several topics mentioned throughout this paper.

 

References

Haendel, L. (2005). The Function Pointer Tutorials. Introduction to C and C++ Function Pointers, Callbacks and Functors. Source: http://www.newty.de/fpt/zip/e_fpt.pdf

Gamma, E., Helm, R., Johnson, R., & Vlissides, J. (1997). Design Patterns: Elements of Reusable Object Oriented Software. Addison-Wesley Publishing.

 

Appendix 1: Event Handling Example

We shall show here how to implement an event handler in VBScript for a typical login flow in QTP. The flow includes three functions: Login, FirstTest and Logout. In addition, two additional functions (AbortTest and Cleanup) are included for error handling and orderly exiting the test flow. Within the TestFlow Sub, the functions are loaded using a generic function (class) loader function (GetFunction), and then called according to their order in the arrRunActions, with their corresponding arguments from arrActionArgs. As can be seen in the code, in case of error the Login.Exec function returns the name of the action to be performed (AbortTest). This action is then performed by means of the statement: strNextAction = dicFunctionHandler(strNextAction)(arrArgs), and since the for loop continues to advance, the normal course of the test flow is altered such that the FirstTest action is never executed.

 

   1: Class Login
   2:     Public Default Function Exec(ByVal arrArgs)
   3:         'Add your code here
   4:         If (Not IsArray(arrArgs)) Then
   5:             MsgBox "Error: username & password are mandatory parameters.", _
   6:                     vbOKOnly+vbCritical, "Login"
   7:             Exec = "AbortTest"
   8:         Else
   9:             MsgBox Join(arrArgs, ";"), vbOKOnly+vbInformation, "Login"
  10:             Exec = ""
  11:         End If
  12:     End Function
  13: End Class
  14:  
  15: Class Logout
  16:     Public Default Function Exec(ByVal arrArgs)
  17:         MsgBox "Exec", vbOKOnly+vbInformation, "Logout"
  18:         'Add your code here
  19:     End Function
  20: End Class
  21:  
  22: Class AbortTest
  23:     Public Default Function Exec(ByVal arrArgs)
  24:         MsgBox "Exec", vbOKOnly+vbCritical, "AbortTest"
  25:         Exec = "Cleanup"
  26:         'Add your code here
  27:     End Function
  28: End Class
  29:  
  30: Class FirstTest
  31:     Public Default Function Exec(ByVal arrArgs)
  32:         MsgBox "Exec", vbOKOnly+vbInformation, "FirstTest"
  33:         'Add your code here
  34:     End Function
  35: End Class
  36:  
  37: Class Cleanup
  38:     Public Default Function Exec(ByVal arrArgs)
  39:         MsgBox "Exec", vbOKOnly+vbInformation, "Cleanup"
  40:         'Add your code here
  41:     End Function
  42: End Class
  43:  
  44: 'This generic function will load any class
  45: Public Function GetFunction(ByVal strFunctionName)
  46:     Execute "Set GetFunction = New " & strFunctionName
  47: End Function
  48:  
  49: 'This sub will run the flow
  50: Public Sub TestFlow(ByVal arrLoadActions, ByVal arrRunActions, ByVal arrActionArgs)
  51:     Dim dicFunctionHandler, ix, arrArgs, strNextAction
  52:     
  53:     Set dicFunctionHandler = CreateObject("Scripting.Dictionary")
  54:     Set arrArgs = CreateObject("Scripting.Dictionary")
  55:     
  56:     'Load the required functions
  57:     With dicFunctionHandler
  58:         For ix = LBound(arrLoadActions) To UBound(arrLoadActions)
  59:             .Add arrLoadActions (ix), GetFunction(arrLoadActions (ix))
  60:         Next
  61:     End With
  62:     
  63:     'Run the required flow
  64:     strNextAction = ""
  65:     For ix = LBound(arrRunActions) To UBound(arrRunActions)
  66:         'Get the action arguments
  67:         arrArgs = split(arrActionArgs(ix), ";")
  68:         If (UBound(arrArgs) - LBound(arrArgs) + 1 = 0) Then
  69:             'If no args are found, pass an empty string
  70:             arrArgs = ""
  71:         End If
  72:         'If the next command is empty
  73:         If (strNextAction = "") Then
  74:             'Run the next planned action
  75:             strNextAction = dicFunctionHandler(arrRunActions(ix))(arrArgs)
  76:         Else
  77:             'Run the action returned by the previously call action
  78:             strNextAction = dicFunctionHandler(strNextAction)(arrArgs)
  79:         End If
  80:     Next
  81: End Sub
  82:  
  83: Call TestFlow(Array("Login", "FirstTest", "Logout", "AbortTest", "Cleanup"), _
  84:               Array("Login", "FirstTest", "Logout"), Array("User;123456″, "", ""))

 

Appendix 2: Implementing a Callback with Function Pointers

We shall show here how to implement a callback with the classes defined in Appendix 1 and used in this paper. The example used here will be the calculation of the total amount plus the tax on the sum of several items prices.

   1: 'Define a constant with the tax value multiplier
   2: Const CONST_DBL_VAT = 1.155
   3:  
   4: 'Now, by dynamically loading the functions without the function handler (as shown earlier in this article):
   5: 'Execute the (*) function on the result of the (+) function to calculate the tax on an item
   6: MsgBox GetMultiply(Array(GetSum(Array(23, 56, 78, 95, 114)), CONST_DBL_VAT)), _
   7:                 vbOKOnly, "Result (Tax)"
   8:  

True, the syntax looks complicated, but it is very useful.

Posted in Design Patterns, Meir Bar-Tal's Blog, Using Classes

4 Responses to “Function Pointers in VB Script (revised)”

  1. Implementing a GUI Layer with Classes | AdvancedQTP Says:

    [+]

    [...] such a Business Layer function using the Command Wrapper Design Pattern, as outlined in my article Function Pointers in VB S... ...

  2. heqingbluesky Says:

    [-]

    A high efficiency in implementation can be achieved through function pointer method.

  3. Implementing a Generic Iterator with Function Pointers | AdvancedQTP Says:

    [+]

    [...] the method outlined here below  is based on the basic techniques described in the article Function Pointers in VB Scrip... ...

  4. Implementing a GUI layer with classes (Russian Translation) | AdvancedQTP Says:

    [+]

    [...] шаблон проектирования, как это описано в моей статье Function Pointers in VB Scr... ...

Leave a Reply

You must be logged in to post a comment.

This article was viewed 1183 times