Calling classic code from service code and vice-versa
During the cohabitation period between classic code and code written with classes (what we call service code), it can be interesting to call classic code from service code and vice-versa. Doing this requires a special way to call the code, because the environment running service and classic code are not the same and will see their technical implementation change and diverge more and more. In a near future, a new generation engine will execute the X3 service code. Its principles are the following:
- the new engine will be a dedicated service running on node.js.
- it will be able to run the service. All the instructions usable in service code are supported ( File, For, Call, fmet... for instance).
- it will not run the classic code instructions that are deprecated in service development ( such as Affzo, Infbox for instance).
- The execution of the native code will automatically be done on the new engine when it will be available without any modification in the code. But the legacy code will continue to be executed on the adonix run-time engine
This is why a technique based on dedicated wrappers has been developed to handle this cross-technology calls. This technique can already be used with the current engine and will remain with the new engine, as long as the classic code will be supported.
Take care that there is an overhead cost to use this call. You should avoid it to call a function that runs only a dozen of instructions. This has been more done to call large functions of classic code that have not been reimplemented in service code, or to handle new service consistent functions from classic code.
Note that this description is independent from availability of the new engine. Whenever the service code is executed by the new engine or by an adonix process, the procedure to call the code from a layer to the other will remain the same, because we need to identify the connection points between classic code and service code.
Two main scenarios will be covered:
- Classic code calling remote service code: the ability for Classic code run by a Classic engine to call service code run by the new engine. This is an out-of-process scenario because the calling code and the called code are in different processes. The parameters and the results are marshalled (serialized and sent over a communication channel) across process boundaries.
- Service code calling remote Classic code: similar to the previous case, just in the opposite direction.
Additionally, we will describe the limitations when using classic code and service code.
Note:
It is technically possible to a library that would be able to run both in classic and service mode, but this not recommended.
Calling service code from classic code
Writing classic code can use tables and masks as well as the instructions that are not deprecated in web mode. It is also possible to declare structures similar to classes by using the following instructions:
Local Instance
declares a class (but only a class and not a representation). The rules, the methods, and the operations are not available in classic code (the structure is really seen as a storage structure).NewInstance
instanciates a class.FreeGroup
releases a group.
These classes will act as pure data containers, without any behavior associated to the data. More specifically:
- Properties of all data types will be supported.
- Collections will be supported.
- Accessors, methods and events will not be supported.
It is nevertheless possible to use the following methods on a class in classic code:
ADDLINE
adds a line in a collection of a class.ADELLINE
deletes a line in a collection of a class.- The context and the corresponding methods are available (for instance
GACTX.APARAM.AGETVALPAR
)
This allows notably to prepare instances that can directly be transferred with the gateway structure.
Classic code cannot use representations.
Calling service code from classic code uses the following elements:
The gateway structure
This structure is the communication structure used to call service code calls from classic page code. The declaration of this structure is done as a normal instance. For example, if we want to declare a gateway structure called MYGATEWAY, you will write the following code:# This is classic code
Local Instance MYGATEWAY Using C_AGATEWAYADX
MYGATEWAY= NewInstance C_AGATEWAYADX Allocgroup null
Writing values into the gateway instance
Once this structure is instantiated, you can fill this structure with any type of value including single values, arrays, and even instances. This is done by using several methods:
Method name | Type of parameter |
---|---|
ASETVALTYNIINT | Tinyint |
ASETVALSHORTINT | Shortint |
ASETVALINTEGER | Integer |
ASETVALDEC | Decimal |
ASETVALDOUBLE | Double |
ASETVALCHAR | Char |
ASETVALDATE | Date |
ASETVALBLOB | Blbfile |
ASETVALCLOB | Clbfile |
ASETVALUUID | Uuident |
ASETVALDATETIME | Datetime |
ASETVALINST | Instance |
These methods has two arguments:
* the first is a string that contains a name that identifies the parameter.
* the second is the variable containing the parameter.
It returns [V]CST_AOK
or [V]CST_AERROR
.
Any parameter can be a single value or an array of values (all values will be sent).
Calling the service code
This is done by using the ARPC method with two arguments: a script name and a function name.
Retrieving the values
After the call, you can read the result from the structure with he same types of values including single values, arrays, and even instances. This is done by using several methods:
Method name | Type of parameter |
---|---|
AGETVALTYNIINT | Tinyint |
AGETVALSHORTINT | Shortint |
AGETVALINTEGER | Integer |
AGETVALDEC | Decimal |
AGETVALDOUBLE | Double |
AGETVALCHAR | Char |
AGETVALDATE | Date |
AGETVALBLOB | Blbfile |
AGETVALCLOB | Clbfile |
AGETVALUUID | Uuident |
AGETVALDATETIME | Datetime |
AGETVALINST | Instance |
Comments
The variables sent in the gateway structure can be different at every call (return values can be different than the calling values, for instance).
An error code is returned if the property name given in AGET* methods cannot be found.
Example of a call
For example, we can do something like this:# Let's call a pricing classic method based on a list of items, a list of quantities,
# a customer code, a date, and a currency code
# We consider that the only value that is changed is the PRICES array and the FREIGHT value,
# although all the other elements are used to define the prices
Local Instance PRICING Using C_AGETEWAYADX
Local Integer RETVAL
Local Char ITEMS(20)(1..)
Local Decimal QUANTITIES(1..)
Local Decimal PRICES(1..), FREIGHT
Local Char CUSTOMER(20)
Local Date ORDER_DATE
Local Char CURRENCY(10)
Local Instance DELIVERY_ADDRESS Using C_ADDRESS
Local Decimal FREIGHT
Gosub FILL_VALUES : # This call fills the variables defined before
PRICING= NewInstance C_AGETEWAYADX Allocgroup null
# Let's fill the gateway structure and handle the errors
# We don't send the FREIGHT value
RETVAL=[V]CST_AERROR
If fmet PRICING.ASETVALCHAR("ITM",ITEMS)=[V]CST_AOK
If fmet PRICING.ASETVALDEC("QTY",QUANTITIES)=[V]CST_AOK
If fmet PRICING.ASETVALCHAR("PRICE",PRICES)=[V]CST_AOK
If fmet PRICING.ASETVALCHAR("CUSTOMER",CUSTOMER)=[V]CST_AOK
If fmet PRICING.ASETVALDATE("ODAT",ORDER_DATE)=[V]CST_AOK
If fmet PRICING.ASETVALCHAR("CUR",CURRENCY)=[V]CST_AOK
RETVAL=fmet PRICING.ASETVALINST("ADDRESS",DELIVERY_ADDRESS)
Endif
Endif
Endif
Endif
Endif
Endif
If RETVAL=[V]CST_AERROR
# Handle the error and exit
Endif
# Let's call the method
RETVAL = fmet PRICING.ARPC("GETPRICES","PRICE_LIB")
If RETVAL=[V]CST_AERROR
# Handle the error and exit
Endif
# Now we can get the result
RETVAL=[V]CST_AERROR
If fmet PRICING.AGETVALCHAR("PRICE",PRICES)=[V]CST_AOK
RETVAL = fmet PRICING.AGETVALDEC("FREIGHT",FREIGHT)
Endif
If RETVAL=[V]CST_AERROR
# Handle the error and exit
Endif
Service code called
The ARPC method executed on a given function from a given script calls a label $GATEWAY that must be present in the script called with the name of the script. Two variables are then available:
- AGATEWAY
is the name the gateway function that has been used in ARPC method
- this
is a the gateway structure that supplies the same access methods (AGETVALxxx, ASETVALxxx) to read or update the properties sent by the classic code.
The list of properties read and written, and the order in which they are written can be changed freely. If an attempt to read a property that does not exist or that doesn't have a compatible type is done, the method returns [V]CST_AERROR
.
Example of service code corresponding to the previous code (PRICE_LIB script)
# Main label called by the ARPC method
$GATEWAY
Case AGATEWAY:
When "GETPRICE" : Gosub GETPRICE
# ...
# Other calls can be implemented in the same script
# ...
Endcase
# Let's imagine the V7+ script uses an ORDER class that includes the properties CUSTOMER, ORDER_DATE, CURRENCY,
# a collection of LINES children instances with ITEM, QUANTITIES, and a DELIVERY instance
$GETPRICE
# List of parameters handled in the gateway
Local Char ITEMS(20)(1..)
Local Decimal QUANTITIES(1..)
Local Decimal PRICES(1..)
Local instance MYORDER Using C_ORDER
Local Integer I, LINE_NO, RETVAL
# Let's instantiate the order
MYORDER=Newinstance C_ORDER Allocgroup null
# Let's read the gateway structure and handle the errors
# FREIGHT has not been sent, PRICES has been sent because some prices might be fixed
RETVAL=[V]CST_AERROR
If fmet this.AGETVALDEC("QTY",QUANTITIES)=[V]CST_AOK
If fmet this.AGETVALCHAR("PRICE",PRICES)=[V]CST_AOK
If fmet this.AGETVALCHAR("CUSTOMER",MYORDER.CUSTOMER)=[V]CST_AOK
If fmet this.AGETVALDATE("ODAT",MYORDER.ORDDATE)=[V]CST_AOK
If fmet this.AGETVALCHAR("CUR",MYORDER.CURRENCY)=[V]CST_AOK
RETVAL=fmet this.AGETVALINST("ADDRESS",MYORDER.DELIVERY)
Endif
Endif
Endif
Endif
Endif
Endif
If RETVAL=[V]CST_AERROR
# Handle the error and exit
Endif
# Let's fill the line collection
For I=1 to maxtab(ITEMS)
LINE_NO=fmet MYORDER.ADDLINE("LINES",[V]CST_ALASTPOS)
If LINE_NO<>[V]CST_ANOTDEFINED
MYORDER.LINES(LINE_NO).ITEM=ITEMS(I)
MYORDER.LINES(LINE_NO).PRICE=PRICES(I)
MYORDER.LINES(LINE_NO).QTY=QUANTITIES(I)
Else
Break
Endif
Next I
If LINE_NO=[V]CST_ANOTDEFINED
# ... Handle the error and exit
Endif
# Now we can manage the MYORDER instance by using any V7+ code
# ...
# Let's fill again the arrays of prices
# The script didn't change the number of lines, so we do not care about ASTALIN
I=0
For LINE_NO=1 to maxtab(MYORDER.LINES)
I+=1
PRICES(I)=MYORDER.LINES(LINE_NO).PRICE
Next I
# Let's write the gateway structure including FREIGHT and handle the errors
RETVAL=[V]CST_AERROR
If fmet this.ASETVALCHAR("PRICE",PRICES)=[V]CST_AOK
RETVAL = fmet this.ASETVALDEC("FREIGHT",MYORDER.FREIGHT)
Endif
If RETVAL=[V]CST_AERROR
# Handle the error and exit
Endif
Return
Calling classic code from service code
The service code execution can only use a subset of the 4GL language. This subset is described precisely in the following document.
A quick overview of the restrictions can be given here:
* screen-oriented instructions (Affzo
, Grizo
, ...) are not supported.
* mask variables ([M]
class) are not supported.
* security sensitive instructions like System
are not supported.
* Some system functions and variables are not supported.
* Inefficient instructions (Read Next
, Read Prev
) are not supported.
* Some esoteric syntax forms will not are supported.
* Global variables are not allowed.
The information which is usually obtained from global variables in Classic code will be read from a context instead. The context can be passed in one of two ways when calling a function or procedure in a common script:
- Explicitly, as a context parameter
- Implicitly, by passing an instance of class. The context will be obtained from the instance as
instance.ACONTEXT
.
The service code can call classic code that only runs with no UI interaction, exactly the kind of code you can run in batch mode.
In this classic code called from a service code, you can of course use masks and tables, but you cannot use instructions such as [Affzo](../4gl/Affzo.md)
.
The gateway structure
The same pattern can be used to perform the call. The only difference is technical gateway class used has another name (C_GATEWAYETNA). So we can give the following example:
# Let's call a Syracuse function to create a record in a class ORDER From a mask (called [M:ORD]).
Local Instance ORDER Using C_AGETEWAYETNA
Local Char ITEMS(20)(1..)
Local Decimal QTY(1..)
Local Decimal PRICE(1..)
Local Integer I
For I=1 To [M:ORD]NBLIN : # Bottom-page variable
ITEMS(I)=[M:ORD]ITEM(I-1)
QTY(I)=[M:ORD]QTY(I-1)
PRICE(I)=[M:ORD]PRICE(I-1)
Next I
# Let's fill the gateway structure and handle the errors
# We don't send the FREIGHT value
RETVAL=[V]CST_AERROR
If fmet ORDER.ASETVALCHAR("ITM",ITEMS)=[V]CST_AOK
If fmet ORDER.ASETVALDEC("QTY",QUANTITIES)=[V]CST_AOK
If fmet ORDER.ASETVALCHAR("PRICE",PRICES)=[V]CST_AOK
If fmet ORDER.ASETVALCHAR("CUSTOMER",[M:ORD]CUSTOMER)=[V]CST_AOK
If fmet ORDER.ASETVALDATE("ODAT",[M:ORD]ORDDATE)=[V]CST_AOK
RETVAL=fmet ORDER.ASETVALCHAR("CUR",[M:ORD]CURRENCY)
Endif
Endif
Endif
Endif
Endif
If RETVAL=[V]CST_AERROR
# Handle the error and exit
Endif
# Let's call the method
RETVAL = fmet ORDER.ARPC("ACREATE","ORDER_LIB")
If RETVAL=[V]CST_AERROR
# Handle the error and exit
Endif
# Let's check if the order creation was successful
RETVAL = fmet ORDER.AGETVAL
The second script would be :
# Main label called by the ARPC method
$GATEWAY
Case AGATEWAY:
When "ACREATE" : Gosub ACREATE
# ...
# Other calls can be implemented in the same library
# ...
Endcase
$ACREATE
# List of parameters handled in the gateway
Local Char ITEMS(20)(1..)
Local Decimal QUANTITIES(1..)
Local Decimal PRICES(1..)
Local instance MYORDER Using C_ORDER
Local Integer I, LINE_NO, RETVAL
# Let's instantiate the order
MYORDER=Newinstance C_ORDER Allocgroup null
# Let's read the gateway structure and handle the errors
RETVAL=[V]CST_AERROR
If fmet this.AGETVALDEC("ITM",ITEMS)=[V]CST_AOK
If fmet this.AGETVALDEC("QTY",QUANTITIES)=[V]CST_AOK
If fmet this.AGETVALCHAR("PRICE",PRICES)=[V]CST_AOK
If fmet this.AGETVALCHAR("CUSTOMER",MYORDER.CUSTOMER)=[V]CST_AOK
If fmet this.AGETVALDATE("ODAT",MYORDER.ORDDATE)=[V]CST_AOK
If fmet this.AGETVALCHAR("CUR",MYORDER.CURRENCY)=[V]CST_AOK
RETVAL=fmet this.AGETVALINST("ADDRESS",MYORDER.DELIVERY)
Endif
Endif
Endif
Endif
Endif
Endif
Endif
If RETVAL=[V]CST_AERROR
# Handle the error and exit
Endif
# Let's fill the line collection
For I=1 to maxtab(ITEMS)
LINE_NO=fmet MYORDER.ADDLINE("LINES",[V]CST_ALASTPOS)
If LINE_NO<>[V]CST_ANOTDEFINED
MYORDER.LINES(LINE_NO).ITEM=ITEMS(I)
MYORDER.LINES(LINE_NO).PRICE=PRICES(I)
MYORDER.LINES(LINE_NO).QTY=QUANTITIES(I)
Else
Break
Endif
Next I
If LINE_NO=[V]CST_ANOTDEFINED
# ... Handle the error and exit
Endif
# Let's use a standard method
RETVAL=Fmet MYORDER.AINSERT
I=fmet ORDER.ASETVALSHORTINT("STA",RETVAL)
If RETVAL<>[V]CST_AOK
# Handle the error (possibly by adding additional return values in the payload) and exit
Endif