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:

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:

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:

These classes will act as pure data containers, without any behavior associated to the data. More specifically:

It is nevertheless possible to use the following methods on a class in classic code:

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 codeLocal Instance MYGATEWAY Using C_AGATEWAYADXMYGATEWAY= 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 nameType of parameter
ASETVALTYNIINTTinyint
ASETVALSHORTINTShortint
ASETVALINTEGERInteger
ASETVALDECDecimal
ASETVALDOUBLEDouble
ASETVALCHARChar
ASETVALDATEDate
ASETVALBLOBBlbfile
ASETVALCLOBClbfile
ASETVALUUIDUuident
ASETVALDATETIMEDatetime
ASETVALINSTInstance

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 nameType of parameter
AGETVALTYNIINTTinyint
AGETVALSHORTINTShortint
AGETVALINTEGERInteger
AGETVALDECDecimal
AGETVALDOUBLEDouble
AGETVALCHARChar
AGETVALDATEDate
AGETVALBLOBBlbfile
AGETVALCLOBClbfile
AGETVALUUIDUuident
AGETVALDATETIMEDatetime
AGETVALINSTInstance

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 pricesLocal Instance PRICING Using C_AGETEWAYADXLocal Integer RETVALLocal Char ITEMS(20)(1..)Local Decimal QUANTITIES(1..)Local Decimal PRICES(1..), FREIGHTLocal Char CUSTOMER(20)Local Date ORDER_DATELocal Char CURRENCY(10)Local Instance DELIVERY_ADDRESS Using C_ADDRESSLocal Decimal FREIGHTGosub FILL_VALUES : # This call fills the variables defined beforePRICING= NewInstance C_AGETEWAYADX Allocgroup null# Let's fill the gateway structure and handle the errors# We don't send the FREIGHT valueRETVAL=[V]CST_AERRORIf fmet PRICING.ASETVALCHAR("ITM",ITEMS)=[V]CST_AOKIf fmet PRICING.ASETVALDEC("QTY",QUANTITIES)=[V]CST_AOKIf fmet PRICING.ASETVALCHAR("PRICE",PRICES)=[V]CST_AOKIf fmet PRICING.ASETVALCHAR("CUSTOMER",CUSTOMER)=[V]CST_AOKIf fmet PRICING.ASETVALDATE("ODAT",ORDER_DATE)=[V]CST_AOKIf fmet PRICING.ASETVALCHAR("CUR",CURRENCY)=[V]CST_AOKRETVAL=fmet PRICING.ASETVALINST("ADDRESS",DELIVERY_ADDRESS)EndifEndifEndifEndifEndifEndifIf RETVAL=[V]CST_AERROR# Handle the error and exitEndif# Let's call the methodRETVAL = fmet PRICING.ARPC("GETPRICES","PRICE_LIB") If RETVAL=[V]CST_AERROR# Handle the error and exitEndif# Now we can get the resultRETVAL=[V]CST_AERRORIf fmet PRICING.AGETVALCHAR("PRICE",PRICES)=[V]CST_AOKRETVAL = fmet PRICING.AGETVALDEC("FREIGHT",FREIGHT)EndifIf RETVAL=[V]CST_AERROR# Handle the error and exitEndif

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$GATEWAYCase 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 gatewayLocal Char ITEMS(20)(1..)Local Decimal QUANTITIES(1..)Local Decimal PRICES(1..)Local instance MYORDER Using C_ORDERLocal Integer I, LINE_NO, RETVAL# Let's instantiate the orderMYORDER=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 fixedRETVAL=[V]CST_AERRORIf fmet this.AGETVALDEC("QTY",QUANTITIES)=[V]CST_AOKIf fmet this.AGETVALCHAR("PRICE",PRICES)=[V]CST_AOKIf fmet this.AGETVALCHAR("CUSTOMER",MYORDER.CUSTOMER)=[V]CST_AOKIf fmet this.AGETVALDATE("ODAT",MYORDER.ORDDATE)=[V]CST_AOKIf fmet this.AGETVALCHAR("CUR",MYORDER.CURRENCY)=[V]CST_AOKRETVAL=fmet this.AGETVALINST("ADDRESS",MYORDER.DELIVERY)EndifEndifEndifEndifEndifEndifIf RETVAL=[V]CST_AERROR# Handle the error and exitEndif# Let's fill the line collectionFor I=1 to maxtab(ITEMS)LINE_NO=fmet MYORDER.ADDLINE("LINES",[V]CST_ALASTPOS)If LINE_NO<>[V]CST_ANOTDEFINEDMYORDER.LINES(LINE_NO).ITEM=ITEMS(I)MYORDER.LINES(LINE_NO).PRICE=PRICES(I)MYORDER.LINES(LINE_NO).QTY=QUANTITIES(I)ElseBreakEndifNext IIf LINE_NO=[V]CST_ANOTDEFINED# ... Handle the error and exitEndif# 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 ASTALINI=0For LINE_NO=1 to maxtab(MYORDER.LINES)I+=1PRICES(I)=MYORDER.LINES(LINE_NO).PRICENext I# Let's write the gateway structure including FREIGHT and handle the errorsRETVAL=[V]CST_AERRORIf fmet this.ASETVALCHAR("PRICE",PRICES)=[V]CST_AOKRETVAL = fmet this.ASETVALDEC("FREIGHT",MYORDER.FREIGHT)EndifIf RETVAL=[V]CST_AERROR# Handle the error and exitEndifReturn

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:

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_AGETEWAYETNALocal Char ITEMS(20)(1..)Local Decimal QTY(1..)Local Decimal PRICE(1..)Local Integer IFor I=1 To [M:ORD]NBLIN : # Bottom-page variableITEMS(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 valueRETVAL=[V]CST_AERRORIf fmet ORDER.ASETVALCHAR("ITM",ITEMS)=[V]CST_AOKIf fmet ORDER.ASETVALDEC("QTY",QUANTITIES)=[V]CST_AOKIf fmet ORDER.ASETVALCHAR("PRICE",PRICES)=[V]CST_AOKIf fmet ORDER.ASETVALCHAR("CUSTOMER",[M:ORD]CUSTOMER)=[V]CST_AOKIf fmet ORDER.ASETVALDATE("ODAT",[M:ORD]ORDDATE)=[V]CST_AOKRETVAL=fmet ORDER.ASETVALCHAR("CUR",[M:ORD]CURRENCY)EndifEndifEndifEndifEndifIf RETVAL=[V]CST_AERROR# Handle the error and exitEndif# Let's call the methodRETVAL = fmet ORDER.ARPC("ACREATE","ORDER_LIB") If RETVAL=[V]CST_AERROR# Handle the error and exitEndif# Let's check if the order creation was successfulRETVAL = fmet ORDER.AGETVAL

The second script would be :
# Main label called by the ARPC method$GATEWAYCase AGATEWAY:When "ACREATE" : Gosub ACREATE# ...# Other calls can be implemented in the same library# ...Endcase$ACREATE# List of parameters handled in the gatewayLocal Char ITEMS(20)(1..)Local Decimal QUANTITIES(1..)Local Decimal PRICES(1..)Local instance MYORDER Using C_ORDERLocal Integer I, LINE_NO, RETVAL# Let's instantiate the orderMYORDER=Newinstance C_ORDER Allocgroup null# Let's read the gateway structure and handle the errorsRETVAL=[V]CST_AERRORIf fmet this.AGETVALDEC("ITM",ITEMS)=[V]CST_AOKIf fmet this.AGETVALDEC("QTY",QUANTITIES)=[V]CST_AOKIf fmet this.AGETVALCHAR("PRICE",PRICES)=[V]CST_AOKIf fmet this.AGETVALCHAR("CUSTOMER",MYORDER.CUSTOMER)=[V]CST_AOKIf fmet this.AGETVALDATE("ODAT",MYORDER.ORDDATE)=[V]CST_AOKIf fmet this.AGETVALCHAR("CUR",MYORDER.CURRENCY)=[V]CST_AOKRETVAL=fmet this.AGETVALINST("ADDRESS",MYORDER.DELIVERY)EndifEndifEndifEndifEndifEndifEndifIf RETVAL=[V]CST_AERROR# Handle the error and exitEndif# Let's fill the line collectionFor I=1 to maxtab(ITEMS)LINE_NO=fmet MYORDER.ADDLINE("LINES",[V]CST_ALASTPOS)If LINE_NO<>[V]CST_ANOTDEFINEDMYORDER.LINES(LINE_NO).ITEM=ITEMS(I)MYORDER.LINES(LINE_NO).PRICE=PRICES(I)MYORDER.LINES(LINE_NO).QTY=QUANTITIES(I)ElseBreakEndifNext IIf LINE_NO=[V]CST_ANOTDEFINED# ... Handle the error and exitEndif# Let's use a standard methodRETVAL=Fmet MYORDER.AINSERTI=fmet ORDER.ASETVALSHORTINT("STA",RETVAL)If RETVAL<>[V]CST_AOK# Handle the error (possibly by adding additional return values in the payload) and exitEndif