Static versus dynamic calls

Calling functions and events

PowerBuilder calls functions and events in three ways, depending on the type of function or event and the lookup method defined.

Type of function

Compiler typing

Comments

Global and system functions

Strongly typed. The function must exist when the script is compiled.

These functions must exist and are called directly. They are not polymorphic, and no substitution is ever made at execution time.

Object functions with STATIC lookup

Strongly typed. The function must exist when the script is compiled.

The functions are polymorphic. They must exist when you compile, but if another class is instantiated at execution time, its function is called instead.

Object functions with DYNAMIC lookup

Weakly typed. The function does not have to exist when the script is compiled.

The functions are polymorphic. The actual function called is determined at execution time.


Specifying static or dynamic lookup

For object functions and events, you can choose when PowerBuilder looks for them by specifying static or dynamic lookup. You specify static or dynamic lookup using the STATIC or DYNAMIC keywords. The DYNAMIC keyword applies only to functions that are associated with an object. You cannot call global or system functions dynamically.

Static calls

By default, PowerBuilder makes static lookups for functions and events. This means that it identifies the function or event by matching the name and argument types when it compiles the code. A matching function or event must exist in the object at compile time.

Results of static calls

Static calls do not guarantee that the function or event identified at compile time is the one that is executed. Suppose that you define a variable of an ancestor type and it has a particular function definition. If you assign an instance of a descendant object to the variable and the descendant has a function that overrides the ancestor's function (the one found at compile time), the function in the descendant is executed.

Dynamic calls

When you specify a dynamic call in PowerBuilder, the function or event does not have to exist when you compile the code. You are indicating to the compiler that there will be a suitable function or event available at execution time.

For a dynamic call, PowerBuilder waits until it is time to execute the function or event to look for it. This gives you flexibility and allows you to call functions or events in descendants that do not exist in the ancestor.

Results of dynamic calls

To illustrate the results of dynamic calls, consider these objects:

  • Ancestor window w_a with a function Set(integer).

  • Descendant window w_a_desc with two functions: Set(integer) overrides the ancestor function, and Set(string) is an overload of the function.

Situation 1

Suppose you open the window mywindow of the ancestor window class w_a:

w_a mywindow
Open(mywindow)

This is what happens when you call the Set function statically or dynamically:

This statement

Has this result

mywindow.Set(1)

Compiles correctly because function is found in the ancestor w_a.

At runtime, Set(integer) in the ancestor is executed.

mywindow.Set("hello")

Fails to compile; no function prototype in w_a matches the call.

mywindow.DYNAMIC Set("hello")

Compiles successfully because of the DYNAMIC keyword.

An error occurs at runtime because no matching function is found.


Situation 2

Now suppose you open mywindow as the descendant window class w_a_desc:

w_a mywindow
Open(mywindow, "w_a_desc")

This is what happens when you call the Set function statically or dynamically in the descendant window class:

This statement

Has this result

mywindow.Set(1)

Compiles correctly because function is found in the ancestor w_a.

At runtime, Set(integer) in the descendant is executed.

mywindow.Set("hello")

Fails to compile; no function prototype in the ancestor matches the call.

mywindow.DYNAMIC Set("hello")

Compiles successfully because of the DYNAMIC keyword.

At runtime, Set(string) in the descendant is executed.


Disadvantages of dynamic calls

Slower performance

Because dynamic calls are resolved at runtime, they are slower than static calls. If you need the fastest performance, design your application to avoid dynamic calls.

Less error checking

When you use dynamic calls, you are foregoing error checking provided by the compiler. Your application is more open to application errors, because functions that are called dynamically might be unavailable at execution time. Do not use a dynamic call when a static call will suffice.

Example using dynamic call

A sample application has an ancestor window w_datareview_frame that defines several functions called by the menu items of m_datareview_framemenu. They are empty stubs with empty scripts so that static calls to the functions will compile. Other windows that are descendants of w_datareview_frame have scripts for these functions, overriding the ancestor version.

The wf_print function is one of these -- it has an empty script in the ancestor and appropriate code in each descendant window:

guo_global_vars.ish_currentsheet.wf_print ()

The wf_export function called by the m_export item on the m_file menu does not have a stubbed-out version in the ancestor window. This code for m_export uses the DYNAMIC keyword to call wf_export. When the program runs, the value of variable ish_currentsheet is a descendant window that does have a definition for wf_export:

guo_global_vars.ish_currentsheet.DYNAMIC wf_export()

Errors when calling functions and events dynamically

If you call a function or event dynamically, different conditions create different results, from no effect to an execution error. The tables in this section illustrate this.

Functions

The rules for functions are similar to those for events, except functions must exist: if a function is not found, an error always occurs. Although events can exist without a script, if a function is defined it has to have code. Consider the following statements:

  1. This statement calls a function without looking for a return value:

    object.DYNAMIC funcname( )
  2. This statement looks for an integer return value:

    int li_int
    li_int = object.DYNAMIC funcname( )
  3. This statement looks for an Any return value:

    any la_any
    la_any = object.DYNAMIC funcname( )

    The following table uses these statements as examples.

    Condition 1

    Condition 2

    Result

    Example

    The function does not exist.

    None.

    Execution error 65: Dynamic function not found.

    All the statements cause error 65.

    The function is found and executed but is not defined with a return value.

    The code is looking for a return value.

    Execution error 63: Function/event with no return value used in expression.

    Statements 2 and 3 cause error 63.


Events

Consider these statements:

  1. This statement calls an event without looking for a return value:

    object.EVENT DYNAMIC eventname( )
  2. This example looks for an integer return value:

    int li_int
    li_int = object.EVENT DYNAMIC eventname( )
  3. This example looks for an Any return value:

    any la_any
    la_any = object.EVENT DYNAMIC eventname( )

The following table uses these statements as examples.

Condition 1

Condition 2

Result

Example

The event does not exist.

The code is not looking for a return value.

Nothing; the call fails silently.

Statement 1 fails but does not cause an error.

 

The code is looking for a return value.

A null of the Any datatype is returned.

La_any is set to null in statement 3.

 

 

If the expected datatype is not Any, execution error 19 occurs: Cannot convert Any in Any variable to datatype.

The assignment to li_int causes execution error 19 in statement 2.

The event is found but is not implemented (there is no script).

The event has a defined return value.

A null of the defined datatype is returned.

If eventname is defined to return integer, li_int is set to null in statement 2.

 

The event does not have a defined return value.

A null of the Any datatype is returned.

La_any is set to null in statement 3.

 

 

If the expected datatype is not Any, execution error 19 occurs: Cannot convert Any in Any variable to datatype.

The assignment to li_int causes execution error 19 in statement 2.

The event is found and executed but is not defined with a return value.

The code is looking for a return value.

Execution error 63: Function/event with no return value used in expression.

Statements 2 and 3 cause error 63.


When an error occurs

You can surround a dynamic function call in a try-catch block to prevent the application from terminating when an execution error occurs. Although you can also handle the error in the SystemError event, you should not allow the application to continue once the SystemError event is invoked -- the SystemError event should only clean up and halt the application.

For information on using try-catch blocks, see the section called “Exception handling in PowerBuilder” in Application Techniques.

If the arguments do not match

Function arguments are part of the function's definition. Therefore, if the arguments do not match (a compatible match, not an exact match), it is essentially a different function. The result is the same as if the function did not exist.

If you call an event dynamically and the arguments do not match, the call fails and control returns to the calling script. There is no error.

Error-proofing your code

Calling functions and events dynamically opens up your application to potential errors. The surest way to avoid these errors is to always make static calls to functions and events. When that is not possible, your design and testing can ensure that there is always an appropriate function or event with the correct return datatype.

One type of error you can check for and avoid is data conversion errors.

The preceding tables illustrated that a function or event can return a null value either as an Any variable or as a variable of the expected datatype when a function or event definition exists but is not implemented.

If you always assign return values to Any variables for dynamic calls, you can test for null (which indicates failure) before using the value in code.

This example illustrates the technique of checking for null before using the return value.

any la_any
integer li_gotvalue
la_any = object.DYNAMIC uf_getaninteger( )
IF IsNull(la_any) THEN
   ... // Error handling
ELSE
   li_gotvalue = la_any
END IF