Getting Started


Version 4.2.459.0

1. Introduction

Getting Started guide serves as an introduction on how the Raincode Legacy Runtime works, how it must be used and how it can be extended. C# and COBOL samples are also included as part of the compiler release and are explained in this document. The COBOL programs are there just as support, to show how the runtime works. They don’t contain anything really relevant, other than showing that everything described here works with plain mainframe COBOL, and that no language extension is needed at all; everything related to integration is done outside of the COBOL programs. For instance, QIX commands are picked up by the compiler as-is; a COBOL CALL can end up calling a C# program.

The samples provided are small enough and contain comments that should be self-sufficient. This guide provides more detailed information (but not as detailed as the complete user guide). It is highly recommended to read the General Overview section before diving into the samples.

2. General Overview

2.1. Execution Flow

This section describes the general execution flow of a legacy program. It describes about how and what the Raincode Legacy Runner (rclrun.exe) does.

rclrun.exe is located in folder %RCDIR%\bin

It can serve as a basis for writing a custome runner infrastructure.

2.1.1. Loading the Module

In a standard C# application, you can start using another .NET assembly’s code by adding a reference to the C# project. At runtime, the framework automatically loads the appropriate DLL (most of the time it will be loaded from the Global Assembly Cache (GAC) or from the same directory as your executable).

The Raincode Legacy Runtime has its own way of resolving assemblies, and does not rely on the automatic mechanism provided by the .NET framework. Therefore, the process is not the same when calling a legacy program. Instead, the runtime must be told where to look for the DLLs; this is done with:

AssemblyLoading.Loader.AddPath(ModuleDictionary.AssemblyCategory, "path_to_dlls");
ModuleDictionary.AssemblyCategory is the category the Raincode Legacy Runtime uses to load legacy assemblies. If you specify something else, the directory won’t be searched when loading a legacy assembly.
The rclrun arguments doing this is AddAssemblySearchDir.

Once the runtime knows where to look for the legacy assemblies, the assembly can be loaded using:

Executable executable = ModuleDictionary.FindExecutable(name);

This method will return null if the executable could not be found, or if the type Executable was not found inside the DLL (this can happen if the DLL is not a legacy assembly).

It is advisable to always check that the executable was found, either by checking manually, or by calling:

ModuleDictionary.EnsureModuleIsLoaded(name);

This will throw an exception if the module was not loaded.

2.1.2. Preparing the Context

The only component that is always required is the execution context (ExecutionContext). It must be instantiated before being able to run any program. It contains all the runtime information needed to properly run the program, like the AddressSpace (AddressSpace), the QIX runtime (see section CICS Support), the SQL runtime (see section SqlRuntime) , condition handling runtime (see section Exceptions) etc. It is used everywhere in the runtime; as such you will often see it as the first parameter in the API.

The easiest way is to use the default constructor:

var ec = new ExecutionContext();

Other overloads allow specification of the desired stack size, or even the size of the AddressSpace. However the default constructor should suit almost all needs.

2.1.3. Execution

The Executable class comes with the following execution method:

  • public void Execute(ExecutionContext ec, CallParameters parms): allows passing arguments and a return value; in case there are no parameters and return value, it can be called with the ExecutionContext only

Argument and return value passing is described in section Passing Parameters between legacy programs and .NET code

2.2. Memory Model

The Raincode Legacy Runtime handles the memory of a program using a large byte array known as the AddressSpace (AddressSpace). There is a single AddressSpace per ExecutionContext. Inside that address space, everything is handled by the Raincode Legacy Runtime.

A pointer inside the AddressSpace is called a MemoryArea, and contains the offset in the address space, as well as the length of the data it points to. Whenever memory must be written or read, a MemoryArea will be involved. Memory can be allocated using the Allocate method on the ExecutionContext:

MemoryArea mem = ec.Allocate(size);

The memory area can then be filled with whatever data is needed.

Memory can (and should) then be freed using:

ec.Deallocate(mem);

Memory whose lifespan does not exceed the life of a function can be allocated on the stack. It will automatically be freed by the runtime when exiting the current Module (COBOL, PL/I or C#, VB.Net, …​).

MemoryArea mem = ec.StackAllocate(size);

Another type of pointer is the TypedLValue, which is a typed pointer. It contains a BitString (a BitString is relevant for PL/I, but not for COBOL. It contains a MemoryArea) and a TypeDescriptor (describes the type of the variable pointed to).

2.2.1. QIX Memory

Sometimes, QIX needs to allocate memory. It is always possible to use the same allocation methods provided by the ExecutionContext, but QIX comes with a higher level API to help the developer. MemoryArea are replaced with IQixPointer, which is similar in concept to MemoryArea.

The IQixContext interface (available through the Context property in a QIX commands instance) has three allocation methods:

  1. IQixPointer QixAllocate(int size): allocates size bytes of memory

  2. void QixFree(IQixPointer mem): frees previously allocated memory

  3. IQixPointer QixReallocate(int size, BaseQixInstance instance): Allocates memory and frees all previously allocated memory by a QIX command of the same type

Using this API allows decoupling the QIX command from the Raincode Legacy Runtime and eases memory management, but it’s totally acceptable to use the allocate method directly on the ExecutionContext.

When using QixReallocate, the runtime keeps track of the allocated memory for each QIX command. To do so, it uses the type and the object instance as the key. Whenever QixReallocate is called, it frees all previously allocated memory for the same QIX command type, as long as the object instance is different; this allows for a single QIX command to allocate memory more than once.

Because of the way QixReallocate works (frees memory for QIX command of the same type, but from a different object instance), singletons can’t be used in the QIX factory, otherwise memory will leak in the address space.
Memory allocated with QixAllocate is not tracked and won’t be freed when calling QixReallocate. Such memory must explicitly be freed by calling QixFree; failure to do so will lead to memory leaks in the underlying AddressSpace; however, because the program memory is managed by the Raincode Legacy Runtime, whenever the ExecutionContext is garbage collected (sometime after the task has finished execution), the underlying AddressSpace is also freed.

2.3. Extending the ExecutionContext

The ExecutionContext contains all runtime information for a given execution of a task. It can be extended to add properties related to your specific needs; the only requirement being that it needs to inherit from the ExecutionContext, and must be casted to the exact type when you require accessing some of your custom functionalities.

2.4. QIX API

2.4.1. QIX command instances

Each QIX command has a corresponding instance in the QIX API, appended with "Instance". For instance, the LINK command counterpart is LinkInstance. Each instance has a set of properties corresponding to the clauses found in the original CICS command, using C# types (as opposed to legacy types, i.e. MemoryArea)

It also contains other properties, not specific to the instance itself:

  • Context: a reference to an IQixContext; in the Raincode Legacy Runtime, the ExecutionContext implements that class, and when used with the legacy compilers, it will always be the ExecutionContext itself. When using the QIX API from C# in the context of a legacy application, it should always be set to the ExecutionContext.

  • DfheiblkHelper: a helper class used to read and write in the CICS Execute Interface Block.

  • Flags: a reference to a QixFlags object. It must be queried to determine if a feature on the QIX commands was enabled or not.

2.4.2. The QIX Factory

Raincode QIX comes with a mechanism allowing for easy extension, using an abstract factory. The QIX factory providing the default implementation is RainCodeLegacyRuntime.QIX.Interface.Factory, inherit from it if you want to replace an implementation provided by the runtime, or provide an implementation for unsupported commands. Each command has a corresponding CreateXXX method; override the ones you want to provide a custom implementation. The custom factory must then be assigned to the QIX interface:

ec.QixInterface.Factory = new MyFactory();
Make sure you always instantiate QIX commands using the factory; this will make the code consistent and makes it easier to update a QIX command.

Refer to the Raincode Legacy Compilers User Guide for a list of implemented QIX commands.

2.4.3. Memory Management

QIX commands expecting to receive data have 2 alternate versions: with an INTO clause, or with a SET clause:

  • With an INTO clause, the memory is already allocated, and you tell QIX to overwrite it; the calling program must tell QIX the maximum length you expect to avoid overwriting the memory beyond the variable itself.

  • On the other end, the SET clause returns a pointer; QIX allocates memory itself and returns the actual allocated length.

In most cases, the name of the property of a QIX command in which the data should be written is called DATA.

QIX commands having the choice between one and the other have the Variant property, with two possible values: Variants.Into and Variants.Set. It is the responsibility of the implementation to test for its value and use the correct allocation scheme:

if (Variant == Variants.Into)

With the INTO variant, data can be copied with the Write method on the QixPointer:

DATA.Write(0, ec.Encoding.GetBytes("Very important data"));

Where the SET variant makes it necessary to allocate the memory first and filled after. The LENGTH clause of the QIX command ends up being automatically set by the QIX runtime, this is why only DATA is set in the sample (there is no LENGTH property). Care must be taken by the implementation to avoid allocating more memory than the value specified in MAXLENGTH.

DATA = Context.QixReallocate(42, this);
DATA.Write(0, ec.Encoding.GetBytes("Very important data"));
The Bytes property on QixPointer returns a copy of the original byte array and cannot be used to update the data to where the pointer points.

2.4.4. IO Runtime

Before being able to run a program using any IO facility (including displaying anything on the command line) you should configure the IO runtime:

executionContext.IOOperation.ConfigureSystemDatasets();

If files are used, configure can also take a FileConfig containing the configuration for all necessary files.

3. Samples

The %RCDIR%\samples\Getting started directory contains ready-to-use C# and COBOL samples illustrating some of the key basic features provided by the Raincode Legacy Runtime. The samples cover aspects such as calling a C# program from COBOL, calling a COBOL program from C#, allocating memory, setting up the QIX runtime, extending the QIX runtime.

3.1. Before Start

Prerequisites
  • Raincode Legacy Compilers and Tools installed: Raincode Net48 Installer or Raincode Net60 Installer

  • Visual Studio 2019 or 2022 depending on the chosen Raincode Legacy Compilers and Tools variant

  • Raincode Language Service Visual Studio extension: Raincode Language Service installer

  • Knowledge of the C# programming language

  • If installed in the Program Files directory, the samples should be copied to the user’s folder to avoid permissions issues

All the samples have their output directory set to ..\out, this makes it easier to resolve assemblies. All samples are a single Visual Studio solution, having the same name as the directory containing them. Most of the samples have at least a COBOL project and a C# project. Samples also contain code not really relevant to the feature being demonstrated, but which are always required (like loading an assembly).

Each sample has a main topic, but might contain other topics, which are needed to be able to run the sample (memory allocation, assembly loading).

The starting project is always called main, so make sure it is correctly set as the starting project before trying to run the sample.

Also note that all C# projects have three mandatory references to the Raincode runtime libraries:

  1. RainCodeLegacyRuntime

  2. RainCodeQixInterface

  3. rccorlib

These three references must always be added when integrating with Raincode legacy programs.

Additionally, in order to build such C# projects, the location of the libraries need to be made available as a user-defined NuGet source. (See: <<../InstallationGuide/InstallationGuide-Windows/InstallationUserGuide-Windows.adocWindowsStackInstallationGuide

In the following parts of this guide, Module and Executable are used interchangeably, even if there is a slight difference between the two (Module is a subclass of Executable).

3.2. call legacy

  • COBOL: contains the COBOL modules that will be called from the C# project

    • BATCHCOB.cob: program called as with a COBOL CALL, no parameter

    • BATCHCOBP: program called as with a COBOL CALL, with parameters

  • main: C# project, entry point

    • Proxy.cs: setup the environment and calls the COBOL programs

This solution demonstrates how a COBOL program can be called using the Raincode Legacy Runtime API. Call must here be understood like a plain COBOL CALL, not like a CICS LINK (the link legacy sample shows how this can be achieved).

The sample issues two calls; one without any parameter, and another with a single parameter (it is easy to extrapolate how multiple parameters can be passed).

The parameterless version of the sample (method CallNoParameter in the main project) is pretty straightforward and does nothing more than what’s explained in the Execution Flow section.

When parameters must be passed (method CallParameter), and based on what the legacy program expects (USING clause of the PROCEDURE DIVISION), the parameters must be allocated in the ExecutionContext and AddressSpace. When using a generated helper structure, the Size field can be used to determine the size of the memory needed. Otherwise, it can either be computed manually (in this sample, we use PIC S9(8) COMP which requires 4 bytes), or, for scalar types, can be obtained from a type descriptor:

var typeDescriptor = FixedDecimalDescriptor.Get(8, 0, Storage.COMP)
MemoryArea memNumber1 = ec.Allocate(typeDescriptor.AllocatedSize);
MemoryArea memNumber2 = ec.Allocate(typeDescriptor.AllocatedSize);

Once allocated, the memory areas must be filled with the value of the parameters. The Raincode Legacy Runtime comes with all the conversion methods needed. These methods can convert data from C# data types to mainframe data types and vice versa, taking care of reading and storing values in the correct format automatically.

RainCodeLegacyRuntime.Types.Convert.move_int32_to_fixed_dec(ec, -15, memNumber1, typeDescriptor);

moves the value 15 to memNumber1.

Before they can be passed to the Execute method (see Execution), they must be wrapped in a CallParameters:

var parms = CallParameters.AllocateParameterList(ec, 2);
parms.SetParameterAddress(ec, 0, memNumber1);
parms.SetParameterAddress(ec, 1, memNumber2);

They can now be used as parameters:

executable.Execute(ec, parms);

3.3. call Csharp

  • csharp: contains the two C# modules that will be called from COBOL

    • CSHELLO.cs: C# program called from a COBOL program, with a single string parameter

    • CSSQUARE: C# program called from a COBOL program, with a structure as parameter

    • WStruct.cs: helper structure for CSSQUARE.cs parameter

  • legacy: contains the COBOL programs calling the C# modules

    • hello.cob: COBOL program calling CSHELLO

    • COBSQUARE.cob: COBOL program calling CSSQUARE

  • main: C# project

    • Program.cs: Setup the environment and calls hello.cob and COBSQUARE.cob

To make a C# class callable from a legacy program, you need to:

  1. Inherit from BaseModule<T>. This makes the class have an interface compatible with the interface used to call legacy programs. The only method to override is Run

  2. Annotate the class with the RainCodeExport attribute. This makes the class identifiable by the ModuleLoader. The exported name will by default be the same as the class, but any other name can be (statically) specified.

This can be seen in the csharp project, on classes CSHELLO and CSSQUARE.

3.3.1. Helper Structure

Class WStruct is generated by the compiler; it is a class that can be used to read and write any legacy data structure easily, using C# data types (the generated code takes care of doing the appropriate conversion). When using the helper structure, the COBOL variable is written right away.

The command line to generate that helper is:

%RCDIR%\bin\cobrc.exe :HelperStructure=W-STRUCT :HelperClassName=RainCode.Samples.WStruct COBSQUARE.cob

CSSQUARE.cs shows how it can be used. It only needs the ExecutionContext and the MemoryArea corresponding to the COBOL structure (the first parameter here). From there, it can be used to either read or write the COBOL memory.

  • COBOL:

    • QIXCOB.cob: COBOL program that will be LINKed to

  • main: C# project

    • Proxy.cs: setup the environment and LINKs to QIXCOB

This sample introduces QIX and its API, it shows how to issue a CICS LINK in C#. It also shows the required steps to have a functional QIX runtime.

The ExecutionContext must be initialized in QIX mode:

var args = new ExecutionContextArgs()
{
    // Enable QIX.
    // automatically creates the RaincodeQixContext and everything needed to run a QIX task
    QixMode = true
};
var ec = new ExecutionContext(args);

From there, the QIX runtime and QIX facilities, like the QixFactory can be used. In this sample a LINK is created:

var link = ec.QixInterface.Factory.CreateLink();

Each clause supported by the QIX command at hand has a corresponding C# property. For instance, telling the program to link to is done with the PROGRAM property:

link.PROGRAM = program;

Similarly to standard COBOL parameters, the COMMAREA must also be allocated using the allocate method of the ExecutionContext. The difference resides in the fact that the MemoryArea must be wrapped in a QixPointer before setting the COMMAREA property:

link.COMMAREA = new QixPointer(commArea);

If you prefer, you can use the QixAllocate method instead as explained in the Memory Management section.

Don’t forget to set the Context property to the ExecutionContext. Remember that the ExecutionContext is the central component and is needed everywhere, including in QIX.

link.Context = ec;

Finally, once all needed parameters are set on the QIX command instance, it can be executed:

ec.QixInterface.Operation.Link(link);
QIX has a complex mechanism of chaining, allowing you to add behavior before or after execution (used for logging for instance); this is why you need to call QixInterface.Operation.Link instead of directly using the Execute method on the LinkInstance (or any other QIX command). However, calling the Execute method directly will not prevent executing the QIX command itself.

In COBOL, you access the Execute Interface Block response value using the variable EIBRESP (automatically added by the CICS compiler on the mainframe); you can access the same variable using the DfheiblkHelper object in the RaincodeQixContext in the ExecutionContext:

ec.RaincodeQixContext.DfheiblkHelper.EIBRESP

3.5. qix_factory

  • COBOL:

    • COBMAIN.cob: issues CICS SEND and CICS RECEIVE commands

  • main:

    • CustomQixFactory.cs: shows how to override the QixFactory

    • CustomReceive: sample implementation of the CICS RECEIVE command

    • CustomSend: sample implementation of the CICS SEND command

    • Program.cs: setup the environment, the QIX environment and LINKs to COBMAIN

This sample shows how a QIX command can be implemented, whether the default implementation does not suit your needs, or if it’s just not implemented. This sample implements both CICS SEND and CICS RECEIVE.

SEND saves message in a queue in memory, and RECEIVE simply de-queues from it and return the message.

3.6. Memory_Allocation

  • COBOL

    • COBDISP.cob: COBOL program expecting an input parameter

  • main

    • Program.cs: setup the environment, allocates memory for the parameter and calls COBDISP

In this sample, the COBOL program expects a parameter in LINKAGE SECTION. The sample is very similar to the call Csharp sample with parameters, but exists for the sake of existing.

3.7. Logger

  • COBOL:

    • hello.cob: COBOL program: does nothing interesting, really

  • main:

    • CustomLogger.cs: shows how a logger can be written to capture all logs issued in the Raincode Legacy Runtime

    • Program.cs: setup the environment, shows how the logger must be registered and calls the COBOL program hello

The Raincode Legacy Runtime has its own logging facility, in the RainCode.Core.Logging namespace. The main class for logging is the static Logger class. It is a static class to which loggers (inheriting from AbstractLogger) can register and be notified whenever something needs to be logged.

In a real-world application, the messages logged by the Raincode runtime can be captured and logged to the actual logging facility (database, files, event logs, ETW…​) This can be done by writing a custom AbstractLogger, override the method

protected override void LoggingFacilityLogged(LogEventArgs e)

and create and register the logger with

RainCode.Core.Logging.Logger.CreateAndRegister<CustomLogger>();

3.8. Plugins

  • hello

    • hello.cob: main entry point, doesn’t do anything but is only there to have a COBOL program that rclrun can execute

  • RclrunExtensions

    • RclrunExtensions.cs: implements a Logger plugin, an ExecutionContext plugin, a SqlRuntime plugin and a QixFactory plugin.

The example only contains a call to Console.WriteLine in each constructor. The goal is just to show that the various plugins are indeed created. The output is:

MyLogger registered
MyExecutionContext created
MySqlRuntime created
MyExecutionContext.Prepare hello
MyQixFactory created
Hello World!

See Plugins for more details.

3.9. BaseDumpProcessor

  • DumpProcessorSample

    • crash.cob: main entry point

    • other0.cob: simple module called by crash.cob but won’t be on the call stack when the program crashes

    • other.cob: program that optionally does a divide by 0, triggering a crash

  • DumpProcessorPlugin

    • MyDumper.cs: simple DumpProcessor that generates an xml file with the dump information

  • dump-result.xml: result of executing the program

The Raincode Legacy runtime allows to create their own BaseDumpProcessor using the plugin interface. For more details refer BaseDumpProcessor

The result is achieved by passing three flags to the COBOL compiler:

  • -TrackPrograms

  • -TrackStatements

  • -Debug (automatic in Debug configuration)

At runtime, the following flag is given to rclrun:

  • -Plugin=DumpProcessorPlugin (the name of the DLL generated by the C# project)

Running this sample will create (in DumpProcessorSample/Debug) a "dump.xml" file.

"dump-result.xml" shows an example result of running this sample.

3.10. SQL Table Caching

  • COBOL: ZIPCACHE, a Cobol program performing different queries in a non-changing table ZIP

  • C#: A SQL table caching plugin setting delegate ExecuteCommandInterceptor so that queries to the ZIP table are cached

  • The ZIP table definition is:

CREATE TABLE [dbo].[ZIP](
       [ZIPCODE] [char](6) NOT NULL,
       [CITY_NAME] [varchar](80) NULL,
       [POPULATION] [decimal](7, 0) NULL
)
  • For the purpose of the example the following values will be used for the connection to the SQL Server:

`Server name` : MY-MACHINE\SQLEXPRESS
`Database name` : TESTDB
`Username` : admin
`Password` : pass1234

The following properties have to be set to execute SQLTableCaching sample:

generaltabprop
Figure 1. General Tab Properties
  • Post-build event in configuration properties uses sqlcmd to load compiler generated stored procedures (the ZIPCACHE.sp.sql file) in the database. For more details, refer Static SQL Execution.

Mind the I option forcing sqlcmd to recognize column names inside double quotes
postbuild
Figure 2. Post build event
connectionstring
Figure 3. SQLServer Connection String

The SQL table caching plugin is activated by setting options Plugin and optionally PluginPath in the Additional Parameters of Run tab

additonalparameters
Figure 4. Additional Parameters

3.11. DB2HisBinder

Only available with Raincode Net48 Installer

An example project showcasing how a DB2 HIS binder can be implemented. The source code can be used freely for your custom implementations.

  • Building the project will produce the following error.

db2his fail
Figure 5. DB2HisBinder build output without specifying Db2HIS_LibraryDir
  • To fix the build issues, please specify the location of IBM Data Server Runtime Client libraries in the bind.csproj project file by defining Db2HIS_LibraryDir project property.

db2his ok
Figure 6. DB2HisBinder build output when Db2HIS_LibraryDir is specified