Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

At first glance each of the above scenarios seems very different. Some of the examples work with already running engines while others dynamically launch engines. Some work with local engines while others connect to engines running on a different machine. Despite these differences, they all use the same basic logic to accomplish their tasks. Therefore, it will be well worth the effort to build a generic or “black box” system for working with multiple engines. This white paper will help you to design this foundation.

Getting Started 
Anchor
Getting Started
Getting Started

Our intent is to provide you everything you need in this document to begin designing applications capable of using multiple engines. Nevertheless, you might be interested in getting a copy of the previously mentioned OI 4.1 New Features.pdf document. In some areas it has more detail regarding the new OpenEngine which might be helpful to programmers who desire to dig a little deeper.

...

ErrorCreateRequest(hRequest, hQueue, ExecLine, Arg1, Arg2, Argn, …)
ErrorReturns error number or 0 if no error (see the References section below.)
hRequestReturns the handle to the request if successful. Variable should be initialized to null beforehand.
hQueueThe handle returned by the CreateQueue function.
ExecLine

Remote process to execute. Use the same syntax as when entering commands in the System Monitor and the System Editor’s Exec line:

Code Block
RUN RoutineName 'Param1', 'Param2', 'Paramn', . . .

Note: Any command line that is passed through the ExecLine parameter will be a literal string. Here is an example of how this might look:

Code Block
ExecLine = "RUN CHECKLEGALUSER 'ADMIN', 'MYSECRET'"
Error = CreateRequest(hRequest, hQueue, ExecLine)

As this example demonstrates, parameters are always literals. Addtionally, CreateRequest does not wait for the remote request to finish processing. Therefore we do not have the ability to detect changes that are made to these parameters as we normally would when passing parameters by reference. If this is a requirement then there are two options: 1.) Design the remote request to send back changes through the normal return data process (see the Twiddling Our Thumbs section below), or 2.) use the previously mentioned CallFunction or CallSubroutine commands instead (see the Requests for Dummies section below.) Using these commands, however, means that our local engine will be waiting for the remote engine to finish processing. While this provides us the ability to connect to remote engines, it defeats our purpose of “multitasking”.

Arg1…Argn

Actual arguments to pass into the ExecLine if replaceable parameters are used. In the example used above we embedded the RoutineName parameters in the ExecLine string. CreateRequest can also work with replaceable parameters. Using the CHECKLEGALUSER command this alternative syntax would look like this:

Code Block
ExecLine = "RUN CHECKLEGALUSER #1, #2"
Arg1 = "EXAMPLES"
Arg2 = "MYSECRET"
Error = CreateRequest(hRequest, hQueue, ExecLine, Arg1, Arg2)

Note that the replaceable parameters were not quoted. Otherwise, RemoteRequest will treat them as the literal values to be passed.

...

ErrorReturns error number or 0 if no error (see the References section below.)
hQueueThe handle returned by the CreateQueue function.
Subroutine

Remote subroutine to execute. Unlike CreateRequest, the RUN command is not needed nor is it supported. If the function is local to the database that was specified in the CreateQueue function then only the subroutine name is required. Otherwise, append an asterisk and the application name to the subroutine name:

Code Block
Subroutine = "MyDivSub" ; * Or "MyDivSub*APPNAME" if necessary
Arg1 = 70
Arg2 = 5
ReturnValue = "" ; * Initialize the variable
Error = CallSubroutine(hQueue, Function, Arg1, Arg2, ReturnValue)
Arg1…ArgnArguments (parameters) for the remote subroutine. Unlike RemoteRequest, these arguments can be passed by reference. That is, any changes made to the original values in the remote subroutine will be returned to the variables used in our calling routine.

Twiddling Our Thumbs 
Anchor
Twiddling Our Thumbs
Twiddling Our Thumbs

Once RemoteRequest has successfully executed our process, it is our responsibility to monitor the status of the remote engine. Two functions are available for this: WaitForReply and PollForReply. In the Getting the Big Picture chart above only PollForReply is mentioned. This is because WaitForReply does exactly what its name implies; that is, it waits for a reply from the remote engine before proceeding. Therefore, if our remote process is busy performing a long process then our local engine will be forced to wait until it is completed. Actually, to be technically correct, if the remote process uses the Send_Info or Send_Dyn commands to send information back then WaitForReply will allow the local engine to receive it. WaitForReply must then be called again to wait for the next information update or until the remote process has completed. Because many processes do not use Send_Dyn or Send_Info (like RList selects), WaitForReply is only a little better than using CallFunction or CallSubroutine.

...

Code Block
Loop
   
   Error = PollForReply(hRequest, Status)

   Begin Case

       Case Error
           * An error was returned with the PollForReply function. Retrieve
           * error text and shutdown connection to remote engine.
           GoSub Process_Error
           GoSub Request_Done
           GoSub Return_Results

       Case Status EQ PROCESSING$
           * Only used with PollForReply. Indicates that the remote engine is
           * still processing our request.

       Case Status EQ DATA_AVAILABLE$
           * Information is available when the remote engine uses the Send_Dyn
           * routine in the request.
           GoSub Process_Status
           
       Case Status EQ INFO_AVAILABLE$
           * Information is available when the remote engine uses the
           * Send_Info routine in the request.
           GoSub Process_Status
           
       Case Status EQ INFO_REQUEST$
           * The remote engine is requesting information from the client using
           * the Req_Info routine. Use the SendResponse function to reply.

       Case Status EQ COMPLETED$
           * The remote engine has completed our request (i.e. the end of the
           * program has been reached.)
           GoSub Process_Status
           GoSub Request_Done
           GoSub Return_Results

       Case Status EQ PROC_ERROR$
           * An error occured in the remote engine request. Any Set_Status
           * error codes will be returned.
           GoSub Process_Status
           GoSub Request_Done
           GoSub Return_Results

   End Case

   Yield()

Until Error OR Status EQ COMPLETED$ OR Status EQ PROC_ERROR$
Repeat

...

Whenever any of our functions returns a value other than 0 we need to compare this with the list of errors from the Error Definitions (REVCERRS.H) file (see the [[1]]the References section below for a complete list.) Here is a portion of code that can be used:

...

With this final piece tied together you now have a “black box” approach toward managing remote engines. While this does not exhaust all that is possible with remote engines, we believe this will give many applications a significant enhancement boost in functionality. We hope that you will agree.

References 
Anchor
References
References

Further information we think will be helpful are listed here for your convenience:

...

Code Block
compile Insert REVCAPI_EQUATES

// CreateEngine Constants
* Connect to an existing engine if possible, else create a new one.
* Use this to create an engine connected to another database.
equ CREATE_ENGINE_OPEN_EXISTING$ to 0x000

* Always create a new engine.
* Engine will be created on the same machine as the calling engine.
equ CREATE_ENGINE_CREATE_NEW$ to 0x001

* Only connect to engines that already exist.
* This is the best way to connect to a remote engine.
equ CREATE_ENGINE_OPEN_ALWAYS$ to 0x002

* Create an engine in indexing mode.
equ CREATE_ENGINE_INDEXER$ to 0x010

* Create an engine that will not shut down when the connections end.
equ CREATE_ENGINE_WAIT_ON_CLOSE$ to 0x020

* Keep the engine invisible (e.g. BitOr(CREATE_ENGINE_OPEN_ALWAYS$,
REV_CREATE_ENGINE_NO_UI$)
equ REV_CREATE_ENGINE_NO_UI$ to 0x040

equ UNPROCESSED$    to 0        ;* Server has not begun request.
equ PROCESSING$     to 1        ;* Server is processing request.
equ DATA_AVAILABLE$ to 2        ;* Server has data available.
equ COMPLETED$      to 3        ;* Server has completed request, status
                               ;* information is available.
equ PROC_ERROR$     to 4        ;* Server process failed, status information is
                               ;* available.
equ INFO_AVAILABLE$ to 5        ;* Server has intermediate status information
                               ;* available.
equ INFO_REQUEST$   to 10       ;* Server is requesting information from client.

 declaredeclare function CreateEngine
declare function CreateQueue
declare function CreateRequest
declare function PollForReply
declare function WaitForReply
declare function GetReply
declare function SendResponse
declare function GetStatusText
declare function CloseRequest
declare subroutine CloseRequest
declare function CallSubroutine
declare function CallFunction
declare function CloseQueue
declare subroutine CloseQueue
declare function CloseEngine
declare subroutine CloseEngine

...