RUN statement

Calls an ABL procedure. This procedure can be local to or remote from the current session, external from or internal to the current procedure, and either synchronous or asynchronous. When a local or remote procedure is called synchronously, the calling procedure resumes execution only after the called procedure completes execution. When a remote procedure is called asynchronously, the calling procedure resumes execution immediately after the remote request is sent to the AppServer.

The RUN statement can also call functions or routines that reside in the Windows Dynamic Link Libraries (DLLs) or in UNIX shared libraries. The called routine must first be declared like an ABL internal procedure. The procedure declaration must be in the same file as the RUN statement.

You can also use the RUN statement to create and associate a procedure object with a Web service, and invoke a Web service operation.

Syntax

Use the following syntax to run an external procedure:

RUN
  {   extern-proc-name
     | VALUE ( extern-expression )
     | path-name<<member-name>>
  }
  [ PERSISTENT | SINGLE-RUN | SINGLETON [ SET proc-handle]]
  [ ON [ SERVER ] {server-handle | session-handle }
         [ TRANSACTION DISTINCT ]
         [ ASYNCHRONOUS 
            [ SET async-request-handle]
            [ EVENT-PROCEDURE event-internal-procedure
                [ IN procedure-context]]
         ]
  ]
  [ ( parameter[ , parameter]... ) ]
  [ argument ]...
  [ NO-ERROR ]

Use the following syntax to run an internal procedure:

RUN
  { intern-proc-name | VALUE ( intern-expression) }
  [ IN proc-handle]
  [ ASYNCHRONOUS
       [ SET async-request-handle]
       [ EVENT-PROCEDURE event-internal-procedure
           [ IN procedure-context]]
  ]
  [ ( parameter[ , parameter]... ) ]
  [ NO-ERROR ]

Use the following syntax to create and associate a procedure object with a Web service:

RUN portTypeName[ SET hPortType ] ON SERVER hWebService[ NO-ERROR ] .

Use the following syntax to invoke a Web service operation:

RUN operationName IN hPortType 
  [ ASYNCHRONOUS 
    [ SET async-request-handle] 
    [ EVENT-PROCEDURE event-internal-procedure 
       [ IN procedure-context]]
  [ ( parameter[ , parameter]... ) ]
  [ NO-ERROR ].
extern-proc-name
The name of the (local or remote) external procedure to run. On UNIX, external procedure names are case sensitive; in Windows, they are not. If you specify a relative pathname, ABL searches the directories (and libraries, on platforms that support libraries) defined in the PROPATH environment variable. With extern-proc-name, you can specify a local or remote procedure. In Windows, the pathname cannot contain characters outside of the non-Unicode code page. See OpenEdge Development: Internationalizing Applications for more information about Unicode and code pages.
VALUE ( extern-expression )
An expression that returns the name of the (local or remote) external procedure you want to run.In Windows, the pathname cannot contain characters outside of the non-Unicode code page.
path-name<<member-name>>
The pathname of an r-code library and the name of an r-code file in that library. To specify an r-code file in a library, you must use the double angle brackets as shown. If you specify a relative library pathname, ABL searches the libraries defined in the PROPATH environment variable. In Windows, the pathname cannot contain characters outside of the non-Unicode code page.
PERSISTENT [SET proc-handle]
Specifies that the external procedure be instantiated and run as a persistent procedure.

You can return the handle to the persistent procedure in proc-handle specified as a field, variable, class property, or output parameter defined with the HANDLE data type. If you do not specify proc-handle, you can find the procedure handle for this procedure using the FIRST-PROCEDURE or LAST-PROCEDURE attributes of the SESSION system handle.

For remote persistent procedures, proc-handle returns the proxy (remote) handle to the persistent procedure. You can also find the proxy handle by using the FIRST-PROCEDURE or LAST-PROCEDURE attributes of the server object handle for the AppServer specified with the ON SERVER option.

A persistent procedure creates and maintains its context after it returns to the caller. Other external procedures can access this context through procedure triggers and internal procedures or user-defined functions defined in the persistent procedure. A RUN statement that runs and creates a persistent procedure context is referred to as an instantiating RUN statement. Thus, a persistent procedure has the following characteristics:

  • The procedure does not go out of scope when it returns: its context and most of its allocated resources remain active, including input parameters, widgets, variables, buffers, temp-tables, work tables, and triggers created during procedure execution. However, all static dialog boxes, their child widgets, and related triggers created during its execution are destroyed when the procedure returns to the caller. This makes all other windows and dialog boxes in the application available for input.
  • All buffers passed as parameters to a persistent procedure are treated as local buffers in the persistent context. When the procedure instantiation returns, the output value of the buffer parameter is returned, as usual, to the calling procedure. However, any cursor positioning established during execution of the instantiating RUN statement is lost to the persistent context once the procedure returns; the AVM creates a copy of the buffer parameter and resets its cursors as an initially defined local buffer.
  • If the procedure obtains any schema share locks (through database access) while executing, these remain in effect after the procedure returns, until the procedure is deleted.
  • Each time you run a procedure persistently, you create a new instance of its procedure context. All of its data, buffers, and widgets are duplicated and separately managed by the new instantiation until the procedure instance is deleted.
Note:

If you run an application that creates persistent procedures from an ADE tool (for example, the Procedure Editor or AppBuilder), that tool removes all instances of persistent procedures still created when the application terminates.

Transaction scoping is the same whether you run a procedure as persistent or not. Any transaction which begins inside a persistent procedure is scoped to the block that starts the transaction.

The order of the PERSISTENT option and the ON SERVER option is interchangeable.

You can use PERSIST as an abbreviation for PERSISTENT.

If you want to invoke persistent procedures on a stateless or state-free AppServer, use either SINGLE-RUN or SINGLETON as an alternative to the PERSISTENT option.

SINGLE-RUN [SET proc-handle]
Initializes the specified external procedure to run on an AppServer or on the client (by using a SESSION handle) in single-run mode, but it does not initially run the procedure.

The SINGLE-RUN option is particularly useful for a stateless or state-free AppServer agent because it allows you to call internal procedures or user-defined functions in a remote external procedure without binding the client to a single AppServer agent. In addition, network traffic is reduced when you run a procedure as a single-run.

In single-run mode, the external procedure is instantiated only when an internal procedure or user-defined function that it defines is invoked remotely. The single-run procedure is deleted when the internal procedure or user-defined function completes. Compare with the SINGLETON option, where the singleton procedure remains instantiated after the internal procedure or user-defined function completes. See the description under intern-proc-name for more information on how internal procedures and user-defined functions run in SINGLE-RUN mode.

You can return the proxy handle to the single-run procedure in proc-handle specified as a field, variable, class property, or output parameter defined with the HANDLE data type. If you do not specify proc-handle, you can find the proxy handle for this procedure using the FIRST-PROCEDURE and LAST-PROCEDURE attributes of the server object handle for the AppServer specified using the ON SERVER option.

Each time you call an internal procedure or user-defined function on a single-run procedure, you create a new instance of its procedure context, which only exists during the execution of the internal procedure or user-defined function. All of its data, buffers, and widgets are duplicated and separately managed by the new instantiation until the procedure instance is deleted.

Note:

You must use the ON SERVER option when you use the SINGLE-RUN option.

You cannot use SINGLE-RUN with external procedures that have parameters.

If you attempt to delete a single-run procedure from a client, you only delete the proxy handle. A single-run procedure remains instantiated for an AppServer session only until its instantiating internal procedure or user-defined function completes execution.

When a single-run procedure is invoked and the procedure is already instantiated as a singleton, the AppServer or client deletes the singleton procedure. The procedure is re-instantiated as a single-run. When the execution of the internal procedure or user-defined function completes, the AppServer or client deletes the single-run procedure but does not re-instantiate the procedure as a singleton.

You cannot use the ASYNCHRONOUS option with SINGLE-RUN when instantiating an external procedure. However you can run an internal procedure of a SINGLE-RUN procedure asynchronously using the following syntax: RUN intern_proc-nameINproc-handle. . . ASYNCHRONOUS.

You can use the EXPORT( ) method on the SESSION handle to add a procedure that will be run in single-run mode to the list of authorized remote procedures on the AppServer.

SINGLETON [SET proc-handle]
Initializes the specified external procedure to run on an AppServer or on the client (by using a SESSION handle) in singleton mode, but it does not initially run the procedure.

The SINGLETON option is particularly useful for a stateless or state-free AppServer agent because it allows you to call internal procedures or user-defined functions in a remote external procedure without binding the client to a single AppServer agent. In addition, network traffic is reduced when you run a procedure as a singleton.

In singleton mode, the external procedure is instantiated only when an internal procedure or user-defined-function that it defines is invoked remotely, and only if the singleton procedure is not already instantiated for a previous internal remote invocation.

The external procedure remains instantiated after its internal procedure or user-defined function completes execution. Compare with the single-run mode, where the procedure is deleted after an internal procedure or user-defined function completes execution. With the SINGLETON option, the external procedure remains instantiated and is used the next time one of its internal procedures or user-defined functions is run. See the description under intern-proc-name for more information on how internal procedures and user-defined functions run in singleton mode.

You can return the proxy handle to a singleton procedure in proc-handle specified as a field, variable, class property, or output parameter defined with the HANDLE data type. If you do not specify proc-handle, you can find the proxy handle for this procedure using the FIRST-PROCEDURE and LAST-PROCEDURE attributes of the server object handle for the AppServer specified using the ON SERVER option.

An external procedure called with the SINGLETON option runs in the same way as other procedures, but with these differences:
  • The procedure does not go out of scope when it returns. Most of its allocated resources remain active, including objects, variables, buffers, temp-tables, work tables, and triggers created during procedure execution. However, you should not keep or rely on any context information maintained for a singleton procedure. Each AppServer agent maintains its own context information independent of (and probably differing from) other AppServer agents. Since there is no guarantee which AppServer agent will process a given request, there is no way of knowing what context information will be available.
  • If the procedure obtains any schema share locks (through database access) while executing, these remain in effect after the procedure returns, until the procedure is deleted.
Note:

You must use the ON SERVER option when you use the SINGLETON option.

You cannot use the SINGLETON option with external procedures that have parameters.

Only one instance of a given singleton procedure exists for a given AppServer agent session, no matter how many clients initialize the procedure with the SINGLETON option. Therefore, all clients access the same instance of a singleton procedure in requests to the same AppServer agent.

If you attempt to delete a singleton procedure from a client using the DELETE PROCEDURE statement, you only delete the proxy handle. A singleton remains instantiated for an AppServer session until session code deletes it or the session terminates.

When a single-run procedure is invoked and the procedure is already instantiated as a singleton, the AppServer or client deletes the singleton procedure. The procedure is re-instantiated as a single-run. When the execution of the internal procedure or user-defined function completes, the AppServer or client deletes the single-run procedure but does not re-instantiate the procedure as a singleton.

You cannot use the ASYNCHRONOUS option with SINGLETON when instantiating an external procedure. However you can run an internal procedure of a SINGLETON procedure asynchronously using the following syntax: RUN intern-proc-nameINproc-handle. . . ASYNCHRONOUS.

You can use the EXPORT( ) method on the SESSION handle to add a procedure that will be run as a singleton to the list of authorized remote procedures on the AppServer.

ON [SERVER]server-handle
Tells the AVM to run the procedure remotely in the AppServer that the HANDLE variable, server-handle , refers to.

With the ASYNCHRONOUS option, server-handle causes the called procedure to run asynchronously in the remote session. Control returns immediately to the statement following the RUN statement. Execution of any specified event-internal-procedure occurs in the context of an input-blocking or PROCESS EVENTS statement.

The order of any PERSISTENT, SINGLE-RUN, or SINGLETON option and the ON SERVER option is interchangeable.

ON [SERVER]session-handle
Tells the AVM to run the procedure locally in the current ABL session, specified by the value of the SESSION system handle (session-handle).

With the ASYNCHRONOUS option, session-handle causes the called procedure to run synchronously in the local session, followed immediately by execution of any specified event-internal-procedure. Only after execution of the specified event-internal-procedure does control return to the statement following the RUN statement.

Note: This order of execution is different than for a remote procedure call using the server-handle.

The order of the PERSISTENT, SINGLE-RUN, or SINGLETON option and the ON SERVER option is interchangeable.

TRANSACTION DISTINCT
Tells the AVM not to propagate the calling procedure's transaction to the AppServer. Although the current version of ABL does not allow transaction propagation, future versions might. Thus, to accommodate this possibility without breaking current code, the current version of ABL allows you to specify this option with server-handle.
Note: It is an error to specify TRANSACTION DISTINCT with a session-handle.
ASYNCHRONOUS [ SET async-request-handle]
Specifies that the remote procedure is to be called as an asynchronous request. By default, the remote procedure is called synchronously. The handle to the asynchronous request is returned in async-request-handle, which must be a field, variable, or parameter defined with the HANDLE data type. If you specify ASYNCHRONOUS but do not specify SET async-request-handle, you can find the handle for the asynchronous request using the LAST-ASYNC-REQUEST attribute of the server-handle specified by the ON option. You can also locate the asynchronous request handle by walking the chain between the FIRST-ASYNC-REQUEST and LAST-ASYNC-REQUEST attributes of server-handle, searching on the PROCEDURE-NAME attribute of each request handle.

For a Web service operation invoked asynchronously, the handle that is set to the asynchronous request object created for the asynchronous request.

Note: You cannot use the ASYNCHRONOUS option with either the SINGLE-RUN or SINGLETON option when instantiating an external procedure. However you can run an internal procedure of a SINGLETON procedure asynchronously using the following syntax: RUN internal_procedureINproc-handle. . . ASYNCHRONOUS.
EVENT-PROCEDURE event-internal-procedure
Specifies a quoted string or character expression representing the name of an internal procedure that resides within procedure-context. When the response from the asynchronous request is received (that is, a PROCEDURE-COMPLETE event occurs), the specified internal procedure is called during subsequent execution of a PROCESS EVENTS or input-blocking statement (such as WAIT-FOR). The specified event-internal-procedure processes any parameters and errors returned from the asynchronous request. If not specified, no event procedure is executed when the PROCEDURE-COMPLETE event occurs for the asynchronous request.

For information on how the event-internal-procedure handles parameters from the asynchronous request, see the parameter option. For information on how the event-internal-procedure handles errors from the asynchronous request, see the NO-ERROR option.

IN procedure-context
A handle to an active procedure that contains the internal procedure specified by event-internal-procedure. If not specified, THIS-PROCEDURE is used as the procedure-context value.
( parameter [ , parameter ] ... )
Specifies one or more parameters to pass to the called procedure.

For the parameter passing syntax, see the Parameter passing syntax reference entry.

Parameters must be defined in the called procedure. (See the DEFINE PARAMETER statement reference entry.) They must be passed in the same order as they are defined, and they must have compatible data types. The AVM attempts to convert values for data types that do not match. If the AVM cannot convert the value for a mismatched data type, the RUN statement fails with an error condition.

For OUTPUT parameters of an asynchronous remote procedure call only, you can specify parameter-name AS primitive-type-name as a prototype. The parameter-name is an arbitrary place-holder name and primitive-type-name must specify the ABL data type of the corresponding OUTPUT parameter in the asynchronous remote procedure. You can also specify OUTPUT parameters for an asynchronous remote procedure using a local field, variable, TABLE temp-table-name, TABLE-HANDLE temp-table-handle, DATASET dataset-name, or DATASET-HANDLE dataset-handle. However, note that the asynchronous remote procedure does not return any values to OUTPUT or INPUT-OUTPUT parameters on the RUN statement. These parameters are place holders only for values returned by the remote procedure to the specified event-internal-procedure.

Any specified event-internal-procedure can define only INPUT parameters and must define one INPUT parameter for each OUTPUT or INPUT-OUTPUT parameter defined in the asynchronous remote procedure. Each event-internal-procedure INPUT parameter must match the corresponding remote procedure OUTPUT or INPUT-OUTPUT parameter in order and data type. (As with other procedures, the AVM attempts to convert the values for data types that do not match.) The asynchronous remote procedure returns the values of these parameters to the INPUT parameters of the event-internal-procedure after the remote procedure completes execution and the client session processes the associated PROCEDURE-COMPLETE event.

If you are running an internal procedure declared as a Windows dynamic link library (DLL) or UNIX shared library routine, you must match any RETURN parameter specified by a DEFINE PARAMETER statement with a corresponding OUTPUT parameter in the RUN statement. If the internal procedure does not specify the RETURN parameter, do not specify the corresponding OUTPUT parameter in the RUN statement.

For external procedures, the parenthesized list of run-time parameters must precede any compile-time arguments.

argument

A constant, field name, variable name, or expression that you want to pass as a compile-time argument to the external procedure you are running.

When you pass arguments to an external procedure, the AVM converts those arguments to character format. ABL recompiles the called procedure, substitutes arguments, and then runs the procedure. You cannot precompile a procedure to which you pass arguments. (If you use shared variables instead of arguments, the procedure can be precompiled. This yields more efficient code.)
Note: You cannot pass compile-time arguments in a call to an internal procedure.
NO-ERROR (RUN statement specific behavior)
Note: See the next entry for a description of general NO-ERROR option behavior. This entry describes special cases for the RUN statement.

Specifies that any ERROR conditions that occur in the attempt to run the procedure are suppressed. This does not mean that all errors produced by the called procedure are suppressed; only errors caused by the RUN statement itself. Also, if a specified local or synchronous remote procedure performs a RETURN ERROR, an ERROR is raised for the RUN statement. After the RUN statement completes, you can check the ERROR-STATUS system handle for information on any errors that occurred.

For an asynchronous remote procedure, the result depends on where the errors occur. If the errors occur during the send phase of the asynchronous request, this raises the ERROR condition on the RUN statement in the client (which you can suppress with NO-ERROR). If the errors occur during execution of the remote request and are returned by the AppServer, this results in an implied NO-ERROR on the RUN statement, and you must check the ERROR-STATUS system handle as well as the attributes of the asynchronous request handle (async-request-handle) for any error returns in the specified event-internal-procedure. If the asynchronous remote procedure returns an unhandled STOP condition, ERROR-STATUS:ERROR and async-request-handle:ERROR are both set to FALSE and async-request-handle:STOP is set to TRUE.

The RUN statement returns ERROR or STOP for a variety of events depending on the type of procedure that is executed, which includes any of the following:

  • All types of procedures
  • Local procedures
  • All remote procedures
  • Synchronous remote procedures
  • Asynchronous remote procedures

The following table summarizes when the AVM raises ERROR or STOP for each type of procedure.

RUN statement ERROR and STOP conditions
Procedure type Condition Event
All procedures ERROR The run-time parameters are not compatible.
ERROR Any specified IN proc-handle option is invalid.
ERROR A called internal procedure is not found in the specified external procedure.
ERROR The procedure returns ERROR.
STOP The procedure returns an unhandled STOP.
Local procedures STOP The specified procedure is not found.1
STOP An attempted compile of the procedure failed.1
All remote procedures ERROR The specified procedure is not found.
ERROR An attempted compile of the procedure failed.
ERROR The specified ON SERVER server-handle option is invalid.
ERROR The server-handle is not currently connected to some AppServer.
ERROR One of the parameters specified by parameter has a data type of BUFFER.
ERROR The PROXY attribute of proc-handle (from the IN proc-handle option) is TRUE and the associated server handle is no longer connected to an AppServer.
Synchronous remote procedures ERROR The ASYNC-REQUEST-COUNT attribute on the server-handle is greater than zero (0).
Asynchronous remote procedures ERROR The REMOTE attribute of procedure-context is set to TRUE.

In addition, under the following conditions, a STOP condition occurs in the context of the input-blocking or PROCESS EVENTS statement that invokes any specified event-internal-procedure:

  • The AVM cannot locate the specified event-internal-procedure, for example, because the spelling of event-internal-procedure is not identical to the name of the internal procedure definition intended for use as the event procedure.
  • The procedure handle that specifies the procedure-context to contain the definition of event-internal-procedure is not a valid procedure handle.
Note: For single-run or singleton procedures, an ERROR or STOP condition in the main block is raised during the execution of an internal procedure or user-defined function.
NO-ERROR (general behavior)
Note: See the previous entry for a description of specific RUN statement NO-ERROR option behavior. This entry describes general NO-ERROR behavior.
Suppresses ABL errors or error messages that would otherwise occur and diverts them to the ERROR-STATUS system handle. If an error occurs, the action of the statement is not done and execution continues with the next statement. If the statement fails, any persistent side-effects of the statement are backed out. If the statement includes an expression that contains other executable elements, like methods, the work performed by these elements may or may not be done, depending on the order the AVM resolves the expression elements and the occurrence of the error.

To check for errors after a statement that uses the NO-ERROR option:

  • Check the ERROR-STATUS:ERROR attribute to see if the AVM raised the ERROR condition.
  • Check if the ERROR-STATUS:NUM-MESSAGES attribute is greater than zero to see if the AVM generated error messages. ABL handle methods used in a block without a CATCH end block treat errors as warnings and do not raise ERROR, do not set the ERROR-STATUS:ERROR attribute, but do add messages to the ERROR-STATUS system handle. Therefore, this test is the better test for code using handle methods without CATCH end blocks. ABL handle methods used in a block with a CATCH end block raise ERROR and add messages to the error object generated by the AVM. In this case, the AVM does not update the ERROR-STATUS system handle.
  • Use ERROR-STATUS:GET-MESSAGE( message-num ) to retrieve a particular message, where message-num is 1 for the first message.

If the statement does not include the NO-ERROR option, you can use a CATCH end block to handle errors raised by the statement.

Some other important usage notes on the NO-ERROR option:

  • NO-ERROR does not suppress errors that raise the STOP or QUIT condition.
  • A CATCH statement, which introduces a CATCH end block, is analogous to a NO-ERROR option in that it also suppresses errors, but it does so for an entire block of code. It is different in that the error messages are contained in a class-based error object (generated by the AVM or explicitly thrown), as opposed to the ERROR-STATUS system handle. Also, if errors raised in the block are not handled by a compatible CATCH block, ON ERROR phrase, or UNDO statement, then the error is not suppressed, but handled with the default error processing for that block type.
  • When a statement contains the NO-ERROR option and resides in a block with a CATCH end block, the NO-ERROR option takes precedence over the CATCH block. That is, an error raised on the statement with the NO-ERROR option will not be handled by a compatible CATCH end block. The error is redirected to the ERROR-STATUS system handle as normal.
  • If an error object is thrown to a statement that includes the NO-ERROR option, then the information and messages in the error object will be used to set the ERROR-STATUS system handle. This interoperability feature is important for those integrating code that uses the traditional NO-ERROR technique with the newer, structured error handling that features error objects and CATCH end blocks.
intern-proc-name

The name of the (local or remote) internal procedure you want to run. The procedure must be declared in the same procedure file as the RUN statement that calls it unless you specify the IN proc-handle option or use a super procedure.

If you do not specify the IN proc-handle option and there is no internal procedure declared by the specified name, the AVM tries to run an external procedure with the specified name. If the internal procedure is remote, you must specify the IN proc-handle option to identify the remote persistent, single-run, or singleton procedure that defines the internal procedure on an AppServer.

If an internal procedure or user-defined function is run using the proc-handle associated with a single-run procedure, the AppServer agent or client:

  • Instantiates the single-run procedure.
  • Runs the main block.
  • Runs the internal procedure or user-defined function.
  • Returns any output parameters or return values to the caller.
  • Deletes the single-run procedure.

If an internal procedure or user-defined function is run using the proc-handle associated with a singleton procedure, the AppServer agent or client:

  • Instantiates the singleton procedure if it is not already instantiated by a previous client singleton request.
  • Runs the main block.
  • Runs the internal procedure or user-defined function.
  • Returns any output parameters or return values to the caller.
  • The singleton procedure remains instantiated.
Note: For more information on running remote user-defined functions, see the FUNCTION statement section.
VALUE ( intern-expression )

An expression that evaluates to the name of the internal procedure you want to run.

IN proc-handle

Specifies the handle of the external procedure that declares the internal procedure you want to run. You can specify proc-handle as a field, variable, parameter, or expression that specifies a valid procedure handle or proxy (remote) persistent procedure handle.

portTypeName

The name of a Web service PortType as specified in the WSDL file.

hPortType

A handle to a procedure object that encapsulates a Web service operation.

hWebService

A handle to the server object bound to the Web service.

operationName

The name of a Web service operation specified in a WSDL file.

Examples

The following procedure displays a simple menu. The user's selection is stored in the selection variable. The INDEX function returns an integer value that indicates the position of the user's selection in a string of characters ("12345"). If the value in the selection variable is not in the list of values, the INDEX function returns a 0. The VALIDATE statement ensures that the INDEX function did not return a zero. If it did, VALIDATE displays the message, "Not a valid choice".

r-run.p

DEFINE VARIABLE selection AS CHARACTER NO-UNDO FORMAT "x(1)"
  LABEL "Enter Program Choice".
DEFINE VARIABLE programs  AS CHARACTER NO-UNDO FORMAT "x(15)" EXTENT 5.

/* Create the procedures custrpt.p, custedit.p, ordrpt.p, and ordedit.p.*/
ASSIGN
  programs[1] = "custrpt.p"
  programs[2] = "custedit.p"
  programs[3] = "ordrpt.p"
  programs[4] = "ordedit.p"
  programs[5] = "r-exit.p".

REPEAT:
  FORM HEADER TODAY "MASTER MENU" AT 35 STRING(TIME,"hh:mm") to 79.
  FORM SKIP(3)
   "1 - Customer Listing"  AT 30
   "2 - Customer Update" AT 30
   "3 - Order Listing"  AT 30
   "4 - Order Update"   AT 30
   "5 - Quit System"    AT 30
   selection COLON 28 AUTO-RETURN WITH SIDE-LABELS NO-BOX 1 DOWN.    
  
UPDATE selection 
    VALIDATE(INDEX("12345",selection) NE 0, "Not a valid choice").
  HIDE ALL.
  RUN VALUE(programs[INDEX("12345",selection)]).
END.

In the RUN statement, the INDEX function returns the position of the user's selection in a character string. Suppose you chose option 2 from the menu. That option occupies the second position in the "12345" character string. Therefore, the INDEX function returns the number two (2). Using this number, the RUN statement reads, RUN VALUE(programs[2]). According to the assignments at the top of the procedure, the value of programs[2] is custedit.p. Now the RUN statement reads, RUN custedit.p, and the r-run.p procedure runs the custedit.p procedure.

The following two external procedures, r-runper.p and r-perprc.p, illustrate the PERSISTENT and IN proc-handle options of the RUN statement. The first procedure, a non-persistent control procedure, sets up a window to run and manage the second procedure as a persistent procedure.

r-runper.p

DEFINE VARIABLE phand AS HANDLE NO-UNDO.
DEFINE VARIABLE nhand AS HANDLE NO-UNDO.
DEFINE VARIABLE whand AS HANDLE NO-UNDO.

DEFINE BUTTON bStart LABEL "Start Customer Query".
DEFINE BUTTON bRecall LABEL "Recall All Hidden Queries".
DEFINE BUTTON bExit LABEL "Exit".

DEFINE FRAME ControlFrame SKIP(.5) SPACE(2) 
  bStart bRecall bExit SPACE(2) SKIP(.5).

ON CHOOSE OF bStart IN FRAME ControlFrame RUN r-perprc.p PERSISTENT.

ON CHOOSE OF bRecall IN FRAME ControlFrame DO:
  phand = SESSION:FIRST-PROCEDURE.
  DO WHILE VALID-HANDLE(phand):
    IF phand:PRIVATE-DATA = "Customer Browse" THEN
      RUN recall-query IN phand.
    phand = phand:NEXT-SIBLING.
  END.
END.

ON CHOOSE OF bExit IN FRAME ControlFrame DO:
  phand = SESSION:FIRST-PROCEDURE.
  DO WHILE VALID-HANDLE(phand):
    nhand = phand:NEXT-SIBLING.
    IF phand:PRIVATE-DATA = "Customer Browse" THEN
      RUN destroy-query IN phand.
      phand = nhand.
  END.
  APPLY "RETURN" TO THIS-PROCEDURE.
END.

SESSION:SYSTEM-ALERT-BOXES = TRUE.
CREATE WINDOW whand ASSIGN
  TITLE            = "Customer Query Control"
  SCROLL-BARS      = FALSE
  MESSAGE-AREA     = FALSE
  MAX-HEIGHT-CHARS = FRAME ControlFrame:HEIGHT-CHARS
  MAX-WIDTH-CHARS  = FRAME ControlFrame:WIDTH-CHARS.

CURRENT-WINDOW = whand.
ENABLE ALL WITH FRAME ControlFrame.
WAIT-FOR RETURN OF THIS-PROCEDURE.

r-perprc.p

DEFINE VARIABLE custwin AS HANDLE NO-UNDO.

DEFINE BUTTON bName    LABEL "Query on Name".
DEFINE BUTTON bBalance LABEL "Query on Balance".
DEFINE BUTTON bCredit  LABEL "Query on Credit".
DEFINE BUTTON bHide    LABEL "Hide Query".
DEFINE BUTTON bCancel  LABEL "Cancel".

DEFINE QUERY custq FOR Customer.
DEFINE BROWSE custb QUERY custq
  DISPLAY Customer.Name Customer.Balance Customer.CreditLimit Customer.Phone
    WITH 10 DOWN.

DEFINE FRAME CustFrame custb SKIP
    bName bBalance bCredit bHide bCancel.

ON CHOOSE OF bName IN FRAME CustFrame DO:
  custwin:TITLE = "Customers by Name".
  OPEN QUERY custq FOR EACH Customer BY Customer.Name.
END.        

ON CHOOSE OF bBalance IN FRAME CustFrame DO:
  custwin:TITLE = "Customers by Balance".
  OPEN QUERY custq FOR EACH Customer BY Customer.Balance DESCENDING.
END.
  
ON CHOOSE OF bCredit IN FRAME CustFrame DO:
  custwin:TITLE = "Customers by Credit".
  OPEN QUERY custq FOR EACH Customer BY Customer.CreditLimit DESCENDING.
END.

ON VALUE-CHANGED OF BROWSE custb DO:
  IF Customer.Balance >= (Customer.CreditLimit * 0.75) THEN DO:
    BELL.
    MESSAGE "Evaluate" Customer.Name "for credit increase.".
  END.
END.

IF THIS-PROCEDURE:PERSISTENT THEN DO:
  THIS-PROCEDURE:PRIVATE-DATA = "Customer Browse".
  CREATE WIDGET-POOL.
END.

CREATE WINDOW custwin ASSIGN
  TITLE            = "Customer Browser"
  SCROLL-BARS      = FALSE
  MAX-HEIGHT-CHARS = FRAME CustFrame:HEIGHT-CHARS
  MAX-WIDTH-CHARS  = FRAME CustFrame:WIDTH-CHARS.

THIS-PROCEDURE:CURRENT-WINDOW = custwin.

ENABLE ALL WITH FRAME CustFrame.

IF THIS-PROCEDURE:PERSISTENT THEN DO:
  ON CHOOSE OF bCancel IN FRAME CustFrame DO:
    RUN destroy-query.
  END.
  ON CHOOSE OF bHide IN FRAME CustFrame DO:
    custwin:VISIBLE = FALSE.
  END.
END.
ELSE
  WAIT-FOR CHOOSE OF bHide, bCancel IN FRAME CustFrame.

PROCEDURE recall-query:
  custwin:VISIBLE = TRUE.
END PROCEDURE.

PROCEDURE destroy-query:
  DELETE PROCEDURE THIS-PROCEDURE NO-ERROR.
  DELETE WIDGET-POOL.
END PROCEDURE.

The control procedure, r-runper.p, runs r-perprc.p each time you choose the Start Customer Query button. Each time it runs, r-perprc.p creates (instantiates) an additional context instance for the persistent procedure, including an additional window to open customer queries. When you choose the Recall All Hidden Queries button from the control window, r-runper.p calls the recall-query internal procedure in each instance of r-perprc.p to redisplay its window. Similarly, when you choose the Exit button, r-runper.p calls the destroy-query internal procedure in each instance of r-perprc.p to delete its context instance; r-runper.p then applies the RETURN event to itself to terminate by completing the WAIT-FOR statement.

The r-perprc.p procedure sets up a customer query that you can re-open three different ways: by name, by balance, or by credit. Each instance of r-perprc.p maintains a separate query for its own local customer buffer. Note that by testing and setting attributes of the THIS-PROCEDURE system handle, r-perprc.p can run either persistently or non-persistently. The basic difference is how the procedure maintains its own context. For example, when running persistently, it defines a trigger on the bCancel button to run its own deletion procedure, destroy-query, to terminate; when running non-persistently, it completes a WAIT-FOR statement with the bCancel button to terminate.

The following example shows how you might implement an asynchronous request. The procedure r-async.p runs persistently from a user-interface trigger, perhaps in response to a menu choice. This procedure, in turn, sends a request to run runReport.p on an AppServer, which provides an inventory report for the specified date.

When r-async.p returns, the user-interface trigger ends and the application returns to its WAIT-FOR state. The user continues to use the application in the normal way while the inventory report runs on the AppServer.

When runReport.p finishes running, a PROCEDURE-COMPLETE event occurs. This event causes the internal procedure reportDone to run automatically within the context of the application's WAIT-FOR statement. Whatever the user is doing in the application, reportDone displays an alert box indicating whether or not the inventory report completed successfully and the number of lines (numLines) that were output for the report. (The bolded ABL statements indicate the code required to support asynchronous requests to run runReport.p.)

r-async.p

DEFINE INPUT PARAMETER invDate AS DATE NO-UNDO. 

DEFINE VARIABLE sh AS HANDLE NO-UNDO. /* Server handle */
DEFINE VARIABLE ah AS HANDLE NO-UNDO. /* Asynchronous request handle */

CREATE SERVER sh.
sh:CONNECT("-AppService Inventory -H myhost").
RUN runReport.p ON SERVER sh 
  ASYNCHRONOUS SET ah EVENT-PROCEDURE "reportDone" IN THIS-PROCEDURE
  (invDate, OUTPUT numLines AS INTEGER).
RETURN.

PROCEDURE reportDone:
  DEFINE INPUT PARAMETER numLines AS INTEGER.

  IF ah:ERROR OR ah:STOP THEN
    MESSAGE "An error occurred when running your" SKIP
      "Inventory report for" invDate "." SKIP
      "The error is:" ERROR-STATUS:GET-MESSAGE(1)
      VIEW-AS ALERT-BOX.
  ELSE
    MESSAGE "Your Inventory report for " invDate SKIP
      "has completed successfully." SKIP
      numLines "report lines were generated"
      VIEW-AS ALERT-BOX.
  
sh:DISCONNECT().
  DELETE OBJECT sh.
  DELETE OBJECT THIS-PROCEDURE. /* Persistent proc no longer needed */
END PROCEDURE.

This example shows an asynchronous call that potentially returns an error object through the ERROR-OBJECT attribute of the Asynchronous request object handle. When server-error.p completes, procDone checks the value of ERROR, and if true, it assigns the instance referenced by ERROR-OBJECT to myAppErrorObj via a cast and then displays the information available, including the attributes inherited from Progress.Lang.Error and an ErrorCode attribute from the user-defined UserDefinedAppError class.

r-client-async-error.p

ROUTINE-LEVEL ON ERROR UNDO, THROW.

DEFINE VARIABLE hRequest AS HANDLE NO-UNDO.
DEFINE VARIABLE hServer AS HANDLE NO-UNDO.
DEFINE VARIABLE lReturn AS LOGICAL NO-UNDO.

CREATE SERVER hServer. 

lReturn = hServer:CONNECT("-pfcspair1.npf").

IF NOT hServer:CONNECTED( ) THEN DO:   
  MESSAGE "Failed to connect to AppServer".
  RETURN.
END. 

RUN server-error.p ON SERVER hServer
  ASYNCHRONOUS SET hRequest EVENT-PROCEDURE "procDone" IN
    THIS-PROCEDURE (OUTPUT numLines AS INTEGER).

WAIT-FOR PROCEDURE-COMPLETE OF hRequest.

DELETE OBJECT hRequest NO-ERROR.
hServer:DISCONNECT().
DELETE OBJECT hServer NO-ERROR.

PROCEDURE procDone:   
  DEFINE INPUT PARAMETER numLines AS INTEGER.
  DEFINE VAR myErrObj AS Progress.Lang.Error.
  DEFINE VAR myAppErrObj AS UserDefinedAppErr.
  DEFINE VAR iTmp AS INTEGER NO-UNDO.
  
  IF hRequest:ERROR THEN DO:
    MESSAGE "An error occurred when running server-error.p".
              
    myErrObj = SELF:ERROR-OBJECT.
    myAppErrObj = CAST (myErrObj, UserDefinedAppErr).
        
    MESSAGE "error object NumMessages: " myErrObj:NumMessages SKIP
      "Severity: " myAppErrObj:Severity SKIP
      "CallStack: " myAppErrObj:CallStack SKIP
      "Error Code: " myAppErrObj:ErrorCode.
      REPEAT iTmp = 1 TO myAppErrObj:NumMessages:
        MESSAGE "ErrorMsg(" iTmp "): " myAppErrObj:GetMessage(iTmp) SKIP 
          "ErrorNum(" iTmp "): " myAppErrObj:GetMessageNum(iTmp).
      END.
    END.        
  ELSE IF NOT hRequest:STOP THEN       
      MESSAGE "server-error.p was has completed successfully." SKIP 
        numLines " was output parameter".
END PROCEDURE.

The example r-singlerun.p illustrates how to run any internal procedure in aproc.p as a single-run. The AppServer does not become bound to the client and network traffic is minimized when you run a procedure as single-run.

When the client executes the RUN aproc.p statement, a proxy handle to the single-run procedure on the AppServer is set without sending a message to the AppServer. When the client sends the request to run internalProcA, the AppServer runs the main block of aproc.p and then runs the internal procedure internalProcA. After internalProcA completes and ends, the AppServer deletes the single-run procedure. The final line deletes the proxy handle on the client without sending a message to the AppServer.

r-singlerun.p

RUN aproc.p SINGLE-RUN SET procHandle ON SERVER serverHandle.
RUN internalProcA IN procHandle (INPUT argIn1, OUTPUT argout1).
DELETE PROCEDURE procHandle.

Compare r-singlerun.p with r-singleton.p. In both cases, the AppServer does not become bound to the client and network traffic is minimized. However, after internalProcA runs in r-singleton.p, the singleton procedure aproc.p is not deleted. It remains instantiated and is available when the call is made to run internalProcB.

r-singleton.p

RUN aproc.p SINGLETON SET procHandle ON SERVER serverHandle.
RUN internalProcA IN procHandle (INPUT argIn1, OUTPUT argout1.
RUN internalProcB IN procHandle (INPUT argIn1, OUTPUT argout1.
DELETE PROCEDURE procHandle.

Notes

See also

{ } Argument reference, { } Include file reference, APPLY statement, Asynchronous request object handle, CODEBASE-LOCATOR system handle, COMPILE statement, CREATE SERVER statement, DEFINE PARAMETER statement, DELETE PROCEDURE statement, DELETE OBJECT statement, ERROR-OBJECT attribute, ON statement, Parameter passing syntax, PROCEDURE statement, Procedure object handle, RUN STORED-PROCEDURE statement, THIS-PROCEDURE system handle, VALID-HANDLE function, Widget phrase