Skip to content

Tutorial 04 05 Add Dispatcher Classes

Matt Linder edited this page Jun 7, 2023 · 23 revisions

Harmony Core Logo

Tutorial 4: Adding Dispatcher Classes for Routines

For each traditional Synergy routine, there must be a dispatcher, which is a class that inherits from the RoutineStub class defined in the Traditional Bridge library code. A dispatcher class has helper methods for serializing and deserializing, exception handling, and logging (see RoutineDispatcher.dbl for more information). Each method dispatcher class:

  • deserializes in and inout parameters from JSON-RPC request messages into suitable variables and prepares variables for out parameters and function return values.
  • calls the underlying subroutine or function, passing parameter variables.
  • serializes inout parameters, out parameters, and the function return value into a JSON-RPC response message.

If you were creating a Traditional Bridge environment from scratch, you would write these classes. But with xfServerPlus migration, code generation generates them for you, as we'll see in the steps that follow. Note that we recommend organizing all dispatcher classes in a project folder and a namespace path.

This is what generic code for a dispatcher class looks like for a method with two in parameters and one out parameter. In this code, there are some placeholders: "" represents the data type for a parameter (e.g., d28.10), "<routine_name>" represents the name of the traditional Synergy routine that the dispatcher is for, and "<interface_name>" represents the name of the (SMC) interface that includes definitions for the Synergy routine:

import Json
import Harmony.TraditionalBridge
import System.Collections
import TraditionalBridge.Models

.ifdef DBLV11
import System.Text.Json
.define JSON_ELEMENT @JsonElement
.else
.define JSON_ELEMENT @JsonValue
.endc

namespace TraditionalBridge.Dispatchers.<interface_name>

    ;;-------------------------------------------------------------------------
    ;;; <summary>
    ;;; Dispatcher for method BridgeMethods.AddTwoNumbers
    ;;; </summary>
    public class <routine_name>_Dispatcher extends RoutineStub

        public method <routine_name>_Dispatcher
        proc
            ;;Initialize the meta data for any data objects that are used by parameters to the method
        endmethod

        protected override method DispatchInternal, void
            required in name,       string
            required in callFrame,  JSON_ELEMENT
            required in serializer, @DispatchSerializer
            required in dispatcher, @RoutineDispatcher
            record
                requestId,          int
                arguments,          JSON_ELEMENT
                argumentDefinition, @ArgumentDataDefinition

                ;;Argument 1 (REQUIRED IN number1 <type>)
                arg1,               d28.10
                ;;Argument 2 (REQUIRED IN number2 <type>)
                arg2,               d28.2
                ;;Argument 3 (REQUIRED OUT result <type>)
                arg3,               d28.10
            endrecord
        proc
            ;;------------------------------------------------------------
            ;;Prepare variables for arguments

            arguments = callFrame.GetProperty("params")

            ;;Argument 1 (REQUIRED IN number1 <type>)

            arg1 = dispatcher.GetImplied(arguments[1])

            ;;Argument 2 (REQUIRED IN number2 <type>)

            arg2 = dispatcher.GetImplied(arguments[2])

            ;;Argument 3 (REQUIRED OUT result <type>)

            ;;------------------------------------------------------------
            ;; Call the underlying routine

            xcall <routine_name>(arg1,arg2,arg3)

            ;;--------------------------------------------------------------------------------
            ;;Argument 3 (REQUIRED OUT result d28.10)
            
            serializer.ArgumentData(3,arg3,FieldDataType.ImpliedDecimalField,28,10,false)
        endmethod

    endclass

endnamespace

Code for Inbound Parameters

As you can see in the code sample above, the arguments variable is used to represent the arguments that were passed to the routine and is populated by the code arguments = callFrame.GetProperty("params"). You can use one of several helper methods that are provided by the RoutineDispatcher class (represented here by the dispatcher variable) to access the data associated with each parameter.

TODO: Document all the helper methods and provide example code here.

Add Dispatcher Classes

As mentioned above, with xfServerPlus migration, code generation generates dispatcher classes from definitions in an SMC. We've selected an interface from our sample SMC (in Add Traditional Bridge Project), so now we'll invoke code generation:

  1. Open a Windows command prompt and navigate to the directory with the .sln file for your Harmony Core solution. Then enter the following command to open the Harmony Core GUI tool:

    harmonycore gui
    
  2. When the "Loading Solution" message disappears, select Codegen > Regen from the menu to generate code for the solution (including code based on the BridgeMethods interface you added in Add Traditional Synergy routines).

  3. In Visual Studio Solution Explorer, right-click the Dispatchers folder in the TraditionalBridge project (TraditionalBridge > Source > Dispatchers) and select Add > Existing Item from the context menu.

  4. In the Add Existing Item window, navigate to the Dispatchers folder, select the BridgeMethodsMethodDispatchers.dbl file, and click Add.

  5. In Visual Studio open the BridgeMethodsMethodDispatchers.dbl file. This file has one class for each traditional Synergy method in the Methods folder (TraditionalBridge > Source > Methods).

Examining GetEnvironment_Dispatcher

Let's take a look at GetEnvironment_Dispatcher (which you'll need to scroll down to). This is a good place to start because the GetEnvironmentroutine has no parameters. It is a function that simply returns an alpha value, which means the dispatcher class must

  • declare a variable to store the return value from the function.
  • call the function and assign the return value to the variable.
  • serialize the return value into the JSON-RPC response to the caller.

Notice that the data division of the DispatchInternal method has a new string variable named returnValue:

    returnValue,        string

This method also includes a call to the Synergy function:

returnValue = %GetEnvironment

And there is code to serialize outbound return values:

 ;;--------------------------------------------------------------------------------
 ;;Argument 3 (REQUIRED OUT result d28.10)        
 serializer.ArgumentData(3,arg3,FieldDataType.ImpliedDecimalField,28,10,false)

This code essentially adds the return value to the JSON-RPC response that will be returned to the caller. The parameters to the ArgumentData method are the following:

  • The argument number, 0, representing the return value of the function
  • The actual value to be returned
  • The data type of the value being returned, using the ENUM FieldDataType. Possible values are
    • AlphaField
    • DecimalField
    • ImpliedDecimal
    • ImpliedDecimalField
    • IntegerField
    • DataObjectField
    • DataObjectCollectionField
    • EnumField
    • HandleField
    • BinaryHandleField
    • StringField
    • AlphaArrayField
    • DecimalArrayField
    • ImpliedDecimalArrayField
    • IntegerArrayField
    • StringArrayField
  • The length of the data being returned
  • The number of decimal places (for implied decimal fields)
  • A Boolean value indicating whether the value contains any binary data

Examining GetLogicalName_Dispatcher

In this case, the routine being called has one inbound parameter and is a function that returns an alpha value. This means the dispatcher class must

  • declare variables for the logical name (to pass to the function) and for the function's return value.
  • call the function, passing the parameter variable and assigning the return value to the return value variable.
  • serialize the return value into the JSON-RPC response to the caller.

Notice that in the data division of the DispatchInternal method, there is an alpha variable named logicalName and a string variable named returnValue:

 logicalName,        a256
 returnValue,        string

The method also has code to extract the value of the inbound parameter from the received JSON-RPC message and store the value in logicalName:

 logicalName = dispatcher.GetText(arguments[1])

There is code to call to the Synergy function:

 returnValue = %GetLogicalName(logicalName)

And there is code to serialize any outbound return value and/or parameters:

 serializer.ArgumentData(0, returnValue,FieldDataType.AlphaField,returnValue.Length,0,true)

Examining AddTwoNumbers_Dispatcher

The AddTwoNumbers routine has three parameters, all defined as numeric, and there are two in parameters and one out parameter. This means the dispatcher class needs to

  • declare variables for the three parameter values.
  • call the subroutine.
  • serialize the out parameter into the JSON-RPC response to the caller.

In the data division of the DispatchInternal method, there are three decimal variables (arg1, arg2 and arg3):

 record
     requestId,          int
     arguments,          JSON_ELEMENT
     argumentDefinition, @ArgumentDataDefinition

     ;;Argument 1 (REQUIRED IN number1 d28.10)
     arg1,               d28.10
     ;;Argument 2 (REQUIRED IN number2 d28.2)
     arg2,               d28.2
     ;;Argument 3 (REQUIRED OUT result d28.10)
     arg3,               d28.10
 endrecord

The method also code to extract the values of the inbound parameters from the received JSON-RPC message and store the values in the arg1 and arg2 variables:

```
;;Argument 1 (REQUIRED IN number1 d28.10)

arg1 = dispatcher.GetImplied(arguments[1])

;;Argument 2 (REQUIRED IN number2 d28.2)

arg2 = dispatcher.GetImplied(arguments[2])
```

There is code to call to the underlying subroutine:

```
xcall AddTwoNumbers(arg1,arg2,arg3)
```

And there is code to serialize the outbound return value:

```
;;--------------------------------------------------------------------------------
;;Argument 3 (REQUIRED OUT result d28.10)
        
serializer.ArgumentData(3,arg3,FieldDataType.ImpliedDecimalField,28,10,false)
```

Next topic: Add Main Dispatcher Class


Clone this wiki locally