Versions Compared

Key

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

...

Much of the following discussion will be familiar to veteran OpenInsight programmers. This is especially true for any who have already reviewed the OI 4.1 New Features.pdf document (see the References section the References section below) that came with the first 4.1 upgrade. If you are generally comfortable with the distinctions between the old and new OpenEngine then you may want to go directly to the Getting Started section the Getting Started section below.

From its humble v1.0 beginnings, OpenEngine (i.e. OENGINE.exe) was designed so that it could stand alone and communicate with a programmer’s IDE of choice. This essentially gave the developer a “client/server” approach to designing OpenInsight applications. As a result, less effort was put into the OpenInsight toolset in lieu of focusing on how OpenEngine can be used by Microsoft C, Visual Basic, Turbo Pascal, and other Windows application tools.

...

ErrorReturns error number or 0 if no error (see the References section below.)
hEngineReturns the handle to the engine connection if successful. Variable should be initialized to null beforehand.
ServerLocation and name of the engine. Format is should be as follows:

\\ServerLocation\ServerName Examples of connecting to an engine using TCP/IP port #777: \\208.252.203.252:777 (Connect to an engine on an IP address) \\ISUPPORT.SRPCS.COM:777 (Connect to an engine on a URL) \\.:777 (Connect to an engine on the local machine) Examples of connecting to an engine using the REP named pipe: \\208.252.203.252\REP (Connect to an engine on an IP address) \\ISUPPORT.SRPCS.COM\REP (Connect to an engine on a URL) \\.\REP (Connect to an engine on the local machine)

DatabaseThe database (.DBT file) the engine should be connected to, like SYSPROG or EXAMPLES.
CreateEngine

Flag(s) to determine how the remote engine should be connected to. These are included in the REVCAPI_EQUATES insert (see the References section below) and are described as follows: 

Code Block
* 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

Another flag, which is undocumented, allows a dynamically launched OpenEngine to be invisible while it is running. This is helpful when the client needs to run local remote engines and the developer does not want the end user to see them on the desktop:

Code Block
* Keep the engine invisible
equ REV_CREATE_ENGINE_NO_UI$ to 0x040

Example of how to use this flag to hide the remote engine:

Code Block
BitOr(CREATE_ENGINE_OPEN_ALWAYS$, REV_CREATE_ENGINE_NO_UI$)
ShutDownBoolean flag to determine if the engine should close itself automatically after the connection is done. This only affects dynamically launched remote engines. We recommend always setting this to 1. There appears to be no benefit keeping the engine open because subsequent requests will not be accepted until it has been closed and created again.

...

ErrorCreateQueue(hQueue, hEngine, QueueName, Database, UserName, Password)
ErrorReturns error number or 0 if no error (see the References section below.)
hQueueReturns the handle to the queue if successful. Variable should be initialized to null beforehand.
hEngineThe handle returned by the CreateEngine function.
QueueNameSet to null.
DatabaseThe database (.DBT file) the engine should be connected to, like SYSPROG or EXAMPLES. Use the same variable/value as in the CreateEngine function.
UserNameAny valid username for the database being connected to.
PasswordThe password for the username being used.

...

Any Last Requests?

...

Once a queue has been established, we are clear to submit a request to our remote engine to execute a stored procedure. While most system stored procedures can be executed through a remote engine, one will normally call custom subroutines and functions in production. There are a few caveats when working with remote engines. Please note the following: 

  1. “Out of process” engines do not run in event context. This means commands like Set_Property, Utility, and OIPI calls will not work. Anything GUI and obviously event logic will not run.
  2. Active selects will not be available once an engine has completed its request and closed the connection. However, one remote request can save a select using the Save_Select command while another remote request activates it using the Activate_Save_Select command. Developers will most likely have a remote request perform the long database query and Save_Select command while allowing the local “in-process” engine to handle the Activate_Save_Select when event context processes (like an OIPI report) can be executed.
  3. Remote engines only have access to resources that are local to the machine running the engine. For instance, using OS-based commands like Drive and DirList will work within the local settings of that machine. In some cases this is desirable. For instance, the remote engine might have access to a drive that is not shared over the network. Hence, a remote request function could provide extremely controlled access to any information (database or OS files) on that drive. Local clients will be prevented from direct access from both the application and network.

...

Remote requests are made with the CreateRequest function: 

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 theTwiddling Our Thumbs section below), or 2.) use the previously mentioned CallFunction or CallSubroutine commands instead (see the Requests the Requests for Dummies section 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.

We recommend using the replaceable parameters for remote requests rather than including them directly in the ExecLine string. In addition to making it easier to code, it adds a layer of security. This can be clearly seen by comparing the following two screen shots:

 

If the machine running the remote engine is in public view then it is possible for sensitive information, like passwords, will be seen in the OpenEngine command log. Using replaceable parameters eliminates this problem. For even more security, remote engines can be launched invisibly using the /HE command line flag: 

C:\OI701\OENGINE.exe /SN=REP /HE EXAMPLES

...

Invisible engines, however, must be shutdown using the Task Manager’s Process tab. 

Requests for Dummies

...

For a simpler and quicker way to make a request to a remote engine we can use two alternative commands: CallFunction andCallSubroutine. In the Getting the Big Picture section above, our chart indicated that these commands replace four separate steps (and a lot of code) that is required when using CreateRequest. This convenience, however, comes at a price: our local engine is suspended until the remote request is completed. Therefore, if you don’t have the time to put together a generic remote request utility that uses CreateRequest, then these commands might be the solution you need: 

ErrorCallFunction(hQueue, ReturnValue, Function, Arg1, Arg2, Argn, …)
ErrorReturns error number or 0 if no error (see the Reference section below.)
hQueueThe handle returned by the CreateQueue function.
ReturnValueThe value returned by the remote function.
Function

Remote function 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 function name is required. Otherwise, append an asterisk and the application name to the function name:

Code Block
Function = "MyDivFunc" ; * Or "MyDivFunc*APPNAME" if necessary
Arg1 = 70
Arg2 = 5
Error = CallFunction(hQueue, ReturnValue, Function, Arg1, Arg2)
Arg1…ArgnArguments (parameters) for the remote function. 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.

...

Error = CallSubroutine(hQueue, Subroutine, Arg1, Arg2, Argn, …) 

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

...

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. 

PollForReply, on the other hand, provides true asynchronous processing which gives us the ability to allow our local engine to perform other tasks while the remote engine is performing its duties. Like WaitForReplyPollForReply will be notified if the remote process uses the Send_Info or Send_Dyn commands. 

Error = PollForReply(hRequest, Status, Reply) 

ErrorReturns error number or 0 if no error (see the References section below.)
hRequestThe handle returned by the CreateRequest function.
StatusFlag to determine the status of the remote process. These are included in the REVCAPI_EQUATES insert (see the References section below) and are described as follows:
 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.
ReplyThis is supposed to contain information that has been returned from the remote engine. However, there is a known bug where this information is actually returned to the Status parameter instead. Fortunately this parameter is optional and we can use two other functions, GetReply and GetResponseText, to get this information.

...

A looping mechanism must now be considered that makes the call to PollForReply. We will use the Status flag to determine if the remote process is done (either because our remote process successfully ended or there was a procedural error) and end our loop. Otherwise, the remote process is still executing. Here is a simple loop to demonstrate how this might look: 

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

...

Note the use of the Yield command within each iteration. This is necessary to give the local engine the ability to process other local requests and, consequently, give us the ability to simulate “multitasking”. Alternatively one could design a window (running invisibly) to use the TIMER property and event to facilitate the looping mechanism. While this makes our coding more complex, it offers two advantages: 

  1. The ability to control the pace of each iteration with the delay parameter of the TIMER property while
  2. putting the burden of the wait mechanism on the operating system rather than OpenInsight.

...

There are now four more areas that need to be covered. These are represented by our Process_Status, Process_Error, Request_Done, and Return_Results GoSub references above. 

What’s Our Status?

...

Each call to the PollForReply function will set the Status flag. If there was a procedural error in the remote request then we will use the GetStatusText function to retrieve the full error text. Otherwise, we will use the GetReply function to retrieve any data that is being returned. Here is an example of how this section of code might look: 

Code Block
Process_Status:

   If Status EQ PROC_ERROR$ then
       * A procedural error occured in the remote request. Use GetStatusText to
       * retrieve the entire error message.
       Error = GetStatusText(hRequest, @RM, StatusText)
   end else
       * Data/Information available. Use GetReply to retrieve the entire text.
       Error = GetReply(hRequest, StatusText)
   end

   If Error then
       GoSub Process_Error
   end else
       If StatusText[-1, 1] EQ \00\ then StatusText[-1, 1] = ""
       *
       * StatusText now contains data which has been returned using Send_Info,
       * Send_Dyn, or the remote function’s Return command. Put whatever logic is
       * desired to manage this information here. Keep in mind that if data
       * received before the Return is needed in the Return_Results then we need
       * to find a way of storing this away until then.
       *
   end
   
return

...

return 

Error Messages

...

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]] section below for a complete list.) Here is a portion of code that can be used: 

Code Block
Process_Error:

   Begin Case
       Case Error EQ 0
           ErrorText = "Successfully Completed"
       Case Error EQ 1
           ErrorText = "Error 1: Invalid name"
       .
       . Put the other error definitions here.
       .
       Case Error EQ 2001
           ErrorText = "Error 2001: REV_ERR_GET_PROC_ADDRESS32"
       Case Error EQ 2002
           ErrorText = "Error 2002: REV_ERR_NO_REVCAP32"
   End Case
   
return

...

Once we have the error description we then need to make the end user aware of the problem. Use whatever method works best for your application (i.e. message box, statusline, error log report, etc.) 

Closing Shop

...

After our remote request is done processing (or in the event of a premature error) we need to properly close our connections. This is perhaps the easiest portion of our code (Note: these must be the same handles that were returned by their respectiveCreateEngineCreateQueue, and CreateRequest functions): 

Code Block
Request_Done:

   CloseRequest(hRequest)
   CloseQueue(hQueue)
   CloseEngine(hEngine)
   
return

...

So Now What?

...

Phew, we finally made it to the end. However, we still might need to do something with the returned data. For instance, if our remote request performed a large table select it might have saved the results using the Save_Select routine and returned the name it was saved under. Our local engine, having received the select name back, can then activate it with the Activate_Save_Selectroutine for a report. 

If we had put all of our above code into a nice tidy routine (i.e. our “black box”) then we have a couple of options available to us. Let’s assume we create a function called Remote_Request: 

Code Block
ReturnValue = Remote_Request(Server, CreateEngine, HideEngine, Database, UserName, -> Password, ExecLine, Arguments, CallBack)

...

One approach would be to call our function within another routine that needs the information being returned (e.g. a report as suggested above.) Thus, our calling routine will wait until the remote request is done processing yet our application will be free to run other processes (due to the embedded Yield command in our status loop.) When the remote request is done processing, our calling routine can then automatically move on or prompt the end user if it is okay to continue. 

Alternatively we can have Remote_Request call another routine for us. This is why the CallBack parameter has been included in theRemote_Request syntax. Ultimately, then, the Call @SubName command will be needed within Remote_Request to make this work. 

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

...

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

1. OI 4.1 New Features.pdf. This document is still available from the Works Download page on Revelation Software’s website. This can be found under the Updated!! OpenInsight Version 4.1 Upgrade link. There are, in fact, two links with the same name. Both will work. Most of the information in this document can also be found in The OpenInsight OpenEngine help file (OpenEngine.chm), which installs in the same directory as OpenInsight.exe. 

2. OENGINE_DLL prototype record. This allows the developer to use Basic+ to connect with remote engines. Note: If you are running OpenInsight v7.0 or higher, this record should already be current: 

Code Block
OENGINE.EXE
*
* internal OEngine functions as defined in rev_call.c module
* (see functions CallDLL and CallOEFunc)
*
VOID INTERNAL POINTER_02(VOID) AS RTP90
VOID INTERNAL POINTER_03(VOID) AS C_PACK
VOID INTERNAL POINTER_04(VOID) AS BLD_TEMPLATE
VOID INTERNAL POINTER_05(VOID) AS OEPUTDATA
VOID INTERNAL POINTER_06(VOID) AS OEREQINFO
VOID INTERNAL POINTER_07(VOID) AS OEPUTSTAT
VOID INTERNAL POINTER_08(VOID) AS OEGETCLIENTSTAT
VOID INTERNAL POINTER_09(VOID) AS RETSTACK
VOID INTERNAL POINTER_22(VOID) AS SET_BGND_IX_TIME
VOID INTERNAL POINTER_23(VOID) AS GETIOFLAG
VOID INTERNAL POINTER_24(VOID) AS SETIOFLAG
VOID INTERNAL POINTER_25(VOID) AS GETENGINEWINDOW
VOID INTERNAL POINTER_26(VOID) AS GETHANDLECOUNT
VOID INTERNAL POINTER_27(VOID) AS SETINITDIROPTIONS
VOID INTERNAL POINTER_28(VOID) AS ISEVENTCONTEXT

* OI 7 - added Remote Engine functions

VOID INTERNAL POINTER_30(VOID) AS CREATEENGINE
VOID INTERNAL POINTER_31(VOID) AS CREATEQUEUE
VOID INTERNAL POINTER_32(VOID) AS CREATEREQUEST
VOID INTERNAL POINTER_33(VOID) AS POLLFORREPLY
VOID INTERNAL POINTER_34(VOID) AS WAITFORREPLY
VOID INTERNAL POINTER_35(VOID) AS GETREPLY
VOID INTERNAL POINTER_36(VOID) AS SENDRESPONSE
VOID INTERNAL POINTER_37(VOID) AS GETSTATUSTEXT
VOID INTERNAL POINTER_38(VOID) AS CLOSEREQUEST
VOID INTERNAL POINTER_39(VOID) AS CALLSUBROUTINE
VOID INTERNAL POINTER_40(VOID) AS CALLFUNCTION
VOID INTERNAL POINTER_41(VOID) AS CLOSEQUEUE
VOID INTERNAL POINTER_42(VOID) AS CLOSEENGINE

* end

...

If you had to update OENGINE_DLL on your system then log into the SYSPROG application and run the following command from the Exec line in the System Editor or from the command line in the System Monitor: 

Code Block
RUN DECLARE_FCNS "OENGINE_DLL"

...

If this is successful, several messages like, “$CLOSEENGINE written to file SYSOBJ.” will appear. 

3. REVCAPI_EQUATES insert record. This insert contains the necessary declarations for those functions defined in the OENGINE_DLL prototype record. Additionally common equates have also been defined. If you are running OpenInsight 7.0 or higher, this record should be current with the exception of one equate: 

Code Block
equ REV_CREATE_ENGINE_NO_UI$ to 0x040

...

However, the version below includes this and all other necessary equates: 

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.

 declare 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

...

4. Error Definitions. Our thanks to Pat McNerthney for making available these undocumented definitions for the numerical errors that can occur. They have been presented in a Begin/End Case syntax for convenience: 

Code Block
Begin Case
   Case Error EQ 0
       ErrorText = "Successfully Completed"
   Case Error EQ 1
       ErrorText = "Error 1: Invalid name"
   Case Error EQ 2
       ErrorText = "Error 2: Out of memory"
   Case Error EQ 3
       ErrorText = "Error 3: Unable to locate server"
   Case Error EQ 5
       ErrorText = "Error 5: Functionality not implemented"
   Case Error EQ 6
       ErrorText = "Error 6: GlobalLock failed"
   Case Error EQ 7
       ErrorText = "Error 7: Null pointer passed to function"
   Case Error EQ 8
       ErrorText = "Error 8: Unknown request status"
   Case Error EQ 9
       ErrorText = "Error 9: Unable to retrieve argument"
   Case Error EQ 10
       ErrorText = "Error 10: Too many requests active"
   Case Error EQ 11
       ErrorText = "Error 11: Invalid request handle"
   Case Error EQ 12
       ErrorText = "Error 12: Invalid DLL procedure name"
   Case Error EQ 13
       ErrorText = "Error 13: File not found"
   Case Error EQ 14
       ErrorText = "Error 14: Undefined error"
   Case Error EQ 15
       ErrorText = "Error 15: Queue does not exist"
   Case Error EQ 16
       ErrorText = "Error 16: The server is not responding"
   Case Error EQ 17
       ErrorText = "Error 17: Max # of clients exceeded for Queue"
   Case Error EQ 18
       ErrorText = "Error 18: Connection to queue failed"
   Case Error EQ 19
       ErrorText = "Error 19: Failed to send message"
   Case Error EQ 20
       ErrorText = "Error 20: Invalid queue handle"
   Case Error EQ 21
       ErrorText = "Error 21: Invalid parameter passed"
   Case Error EQ 22
       ErrorText = "Error 22: Client-server handshaking error"
   Case Error EQ 23
       ErrorText = "Error 23: Script is incomplete"
   Case Error EQ 24
       ErrorText = "Error 24: Data is not available"
   Case Error EQ 25
       ErrorText = "Error 25: Error accessing template"
   Case Error EQ 26
       ErrorText = "Error 26: Invalid column; column not avail"
   Case Error EQ 27
       ErrorText = "Error 27: Invalid type"
   Case Error EQ 28
       ErrorText = "Error 28: Maximum queues exceeded"
   Case Error EQ 29
       ErrorText = "Error 29: The buffer is too small for data"
   Case Error EQ 30
       ErrorText = "Error 30: Invalid Send or Receive sequence"
   Case Error EQ 31
       ErrorText = "Error 31: Invalid user name"
   Case Error EQ 32
       ErrorText = "Error 32: Invalid password"
   Case Error EQ 33
       ErrorText = "Error 33: Invalid pathname"
   Case Error EQ 34
       ErrorText = "Error 34: Invalid database name"
   Case Error EQ 35
       ErrorText = "Error 35: Max # of engines already running"
   Case Error EQ 36
       ErrorText = "Error 36: Unable to start engine"
   Case Error EQ 37
       ErrorText = "Error 37: Unable to shutdown engine"
   Case Error EQ 38
       ErrorText = "Error 38: Unable to create new engine"
   Case Error EQ 100
       ErrorText = "Error 100: REV_ERR_RCLINITIALIZE"
   Case Error EQ 101
       ErrorText = "Error 101: REV_ERR_RCLVERSION"
   Case Error EQ 102
       ErrorText = "Error 102: REV_ERR_RCLOPENCLIENT"
   Case Error EQ 103
       ErrorText = "Error 103: REV_ERR_RCLCONNECT"
   Case Error EQ 104
       ErrorText = "Error 104: REV_ERR_RCLREQUESTREPLY"
   Case Error EQ 105
       ErrorText = "Error 105: REV_ERR_RCLDISCONNECT"
   Case Error EQ 106
       ErrorText = "Error 106: REV_ERR_RCLCLOSECLIENT"
   Case Error EQ 107
       ErrorText = "Error 107: REV_ERR_RCLTERMINATE"
   Case Error EQ 108
       ErrorText = "Error 108: REV_ERR_RCLCHECKSERVER"
   Case Error EQ 200
       ErrorText = "Error 200: REV_ERR_HEAPCREATE"
   Case Error EQ 201
       ErrorText = "Error 201: REV_ERR_HEAPFREE"
   Case Error EQ 202
       ErrorText = "Error 202: REV_ERR_HEAPDESTROY"
   Case Error EQ 203
       ErrorText = "Error 203: REV_ERR_CREATEEVENT"
   Case Error EQ 1000
       ErrorText = "Error 1000: REV_ERR_INVALID_ENGINE_HANDLE"
   Case Error EQ 1001
       ErrorText = "Error 1001: REV_ERR_ENGINE_BUSY"
   Case Error EQ 1002
       ErrorText = "Error 1002: REV_ERR_DATABASE_INIT"
   Case Error EQ 1003
       ErrorText = "Error 1003: REV_ERR_LOGON"
   Case Error EQ 1004
       ErrorText = "Error 1004: REV_ERR_NO_SERVER_MEMORY"
   Case Error EQ 1005
       ErrorText = "Error 1005: REV_ERR_REQUEST_ACTIVE"
   Case Error EQ 1006
       ErrorText = "Error 1006: REV_ERR_REQUEST_NOT_ACTIVE"
   Case Error EQ 1007
       ErrorText = "Error 1007: REV_ERR_UNEXPECTED_PARAMETER"
   Case Error EQ 1008
       ErrorText = "Error 1008: REV_ERR_MISSING_OBJECT"
   Case Error EQ 2000
       ErrorText = "Error 2000: REV_ERR_LOAD_REVCAP32"
   Case Error EQ 2001
       ErrorText = "Error 2001: REV_ERR_GET_PROC_ADDRESS32"
   Case Error EQ 2002
       ErrorText = "Error 2002: REV_ERR_NO_REVCAP32"
   Case 1
       ErrorText = "Unknown Error: ":Error
End Case

...

5. OpenEngine Reference Manual. This is more for developers who are comfortable with programming in other IDEs, especially C++, to communicate through the OpenEngine API. A free copy of this can be downloaded from Revelation Software’s Knowledge Base at this link: http://www.revelation.com/o4wtrs/oecgi3.exe/O4W_RUN_FORM?INQID=KB_ARTICLE_INQUIRY&SEARCH_1=607983FEBE650BC985256957005921B6&SEARCH=1#/section/breadcrumb/UPDATETABLE/Display