Accessing CICS from Microsoft .NET applications using CICS Transaction Gateway


Microsoft® .NET® is a software framework that is similar to Java® in that code is compiled to platform-independent byte code and then run in a virtual machine. IBM® CICS® Transaction Gateway V8 (hereafter called CICS TG) includes a .NET API based on the External Call Interface (ECI) V2 API. The ECI V2 API supports both non-transactional and one-phase-commit calls to CICS.

A .NET application can be used in a three-tier environment to communicate with CICS using CICS TG over TCP/IP. CICS TG can run on the local Windows® system or on a remote IBM z/OS®, UNIX®, Linux®, or Windows system. Two possible topologies are shown in Figure 1:

Figure 1. Example CICS TG topologies
Example CICS TG topologies
Example CICS TG topologies

Scenarios that might incorporate a .NET application include:

  1. Single-threaded batch processing .NET application making calls to CICS
  2. Multi-threaded interactive .NET application making calls to CICS
  3. ASP.NET application deployed in Microsoft IIS making calls to CICS.

In this article, you will write a single-threaded C# application using Microsoft Visual C# 2008 or 2010 to retrieve customer account data from a CICS server via a CICS communications area (COMMAREA). You will then extend the application to enable data members in the COMMAREA to be packed and unpacked and converted to and from the appropriate .NET data types. Finally a graphical user interface (GUI) will be added, and the presentation and CICS communication logic will be separated to run on different threads. The article is aimed at application programmers who have some familiarity with .NET concepts and some experience developing for the .NET environment.

The CICS application for this article is a simple banking application and it is provided in C and COBOL. It provides the four functions listed below, and each function is identified by a single character which the calling application places at the start of the COMMAREA:

List (L) function
Returns a list of account numbers that have the IsPremier flag set to true.
Retrieve (R) function
Returns a single account record based on the account number.
Make Premier (P) function
Sets the IsPremier flag to true for a specified account number.
Update (U) function
Updates an entire customer record.

The program sends and receives data in a 44-byte COMMAREA. If the program encounters an error, it returns an error message in the COMMAREA with the first character set to E. You can download the source code at the bottom of the article.

.NET console application

This section shows you how to create a basic .NET console application in C# that connects to CICS TG and invokes the List (L) function in the DATAFILE CICS program. First, create a new C# console application in Microsoft Visual Studio 2008 or 2010 and add a reference to the CICS TG API for .NET:

  1. Select File => New => Project.
  2. Select Visual C# on the left frame and Console Application on the right frame.
  3. Change the project name to DatafileBasic.
  4. Click OK. Visual Studio creates the project and displays the code for the Program class in Program.cs.
  5. Select Project => Add Reference.
  6. Select the Browse tab and navigate to the location of IBM.CTG.Client.dll.
  7. Click OK and the reference will be added to the project.

The GatewayConnection class represents a connection to CICS TG through which requests can be flowed to CICS. Creating a new instance of the GatewayConnection class opens the connection to the Gateway daemon and the connection remains open until the Close or Dispose method is invoked on the GatewayConnection object, or the connection is lost. The GatewayConnection class implements the IDisposable interface which allows automatic disposal via a C# using block, or Using in Visual Basic. Listing 1 shows how a connection can be opened and automatically closed when the current scope ends:

Listing 1. Creating a connection to the CICS TG
private void Start(string[] args) {
   String hostname = "";
   int port = 2006;

   // Open a connection to the Gateway
   try {
      using (GatewayConnection connection = new GatewayConnection(hostname, port)) {
         // Use the connection

   } catch (GatewayException ge) {
      Console.WriteLine("ERROR: {0}", ge.Message);
   } catch (SocketException se) {
      Console.WriteLine("ERROR: {0}", se.Message);

After the connection has been established, ECI requests can be sent to CICS using the Flow method of the GatewayConnection object. The EciRequest class represents an ECI request and exposes properties that affect the ECI call, such as the name of the CICS server, the name of the CICS program to call, and the contents of the COMMAREA to pass to the CICS program. Listing 2 shows sending an EciRequest that calls the DATAFILE CICS program on server CICS-A, with the COMMAREA set to the contents of a string, and a 5-second timeout:

Listing 2. Calling the DATAFILE CICS program
// Create COMMAREA to pass to the DATAFILE program
string commarea = "L" + new string(' ', 43);

// Set up EciRequest object to call DATAFILE with a 5 second timeout
EciRequest eciReq = new EciRequest();

eciReq.ServerName = "CICS-A";
eciReq.ExtendMode = EciExtendMode.EciNoExtend;
eciReq.Program = "DATAFILE";
eciReq.Timeout = 5;

// Send the request to CICS

Data conversion

When the .NET application and the CICS server are running on different systems with different code pages (for example Windows and z/OS), data must be converted between code pages. Code page conversion can be performed automatically in CICS using the DFHCCNV program or DFHCNV template. However, in some cases it is desirable to perform the conversion in the calling application, which means the client application must be aware of the encoding that the remote CICS program is expecting data to be provided in. The sample program holds an Encoding object named CicsEncoding at the class level that is used to perform code page conversion on any text fields. The sample sets CicsEncoding to EBCDIC (IBM-037) by default, so you must change it if your CICS system runs under a different code page.

Error handling

Three types of errors should be handled by the .NET application:

  • Errors during communication with CICS TG
  • Errors during the CICS call
  • Errors reported by the CICS program itself

Examples of communication errors are the connection between the .NET application and the Gateway daemon being lost, or the Gateway daemon being shut down. These types of errors cause a SocketException or GatewayException to be thrown. The catch handlers shown in Listing 1 above will catch these exceptions and display a message.

Examples of errors during the ECI call include the CICS server being unavailable, security errors, and the ECI call timing out. When these conditions occur, a return code is stored in the EciReturnCode property of the EciRequest object. This property can be tested after the Flow method completes to determine the outcome of the ECI call. The EciReturnCodes enumeration contains all of the possible ECI return codes and a value of EciNoError indicates that the ECI call was successful. A return code of EciErrTransactionAbend indicates that the CICS program experienced an abnormal end (abend) and the 4-character abend code can be retrieved from the AbendCode property of EciRequest. Listing 3 shows how the EciReturnCode property can be used to output a suitable error message:

Listing 3. Handling ECI errors
private void HandleEciError(EciRequest eciReq) {

   // Display a message based on the ECI return code
   switch (eciReq.EciReturnCode) {
   case EciReturnCode.EciErrNoCics:
   case EciReturnCode.EciErrCicsDied:
   case EciReturnCode.EciErrResourceShortage:
   case EciReturnCode.EciErrNoSessions:
      // Communication failure between the CICS TG and CICS
      // or insufficient communication resources
      Console.WriteLine("ERROR: Unable to communicate with the CICS server");

   case EciReturnCode.EciErrResponseTimeout:
      // Response not received within the specified timeout period
      Console.WriteLine("ERROR: Request timed out");

   case EciReturnCode.EciErrSecurityError:
      // Invalid user id or password specified
      Console.WriteLine("ERROR: User not authorized");

   case EciReturnCode.EciErrTransactionAbend:
      // Abnormal end (abend) in CICS
      Console.WriteLine("ERROR: An error occurred in CICS, abend code: {0}", 

      // A different error occurred, so display the return code
      Console.WriteLine("ERROR: An error occurred: {0}", eciReq.EciReturnCode);

The third kind of error is an error returned from the CICS program. As described above, the DATAFILE program reports an error by placing a message into the COMMAREA that is returned to the .NET application. Listing 4 shows you how to check for program errors and display the returned error message:

Listing 4. Handling errors from the DATAFILE CICS program
byte[] outCommarea = eciReq.GetCommareaData();
if (CicsEncoding.GetString(outCommarea, 0, 1)[0] == ' ') {
   // No error occurred, display returned account numbers

} else {
   // Program returned an error in the COMMAREA
   string error = CicsEncoding.GetString(outCommarea, 1, outCommarea.Length - 1);
   error = error.TrimEnd('\0', ' ');
   Console.WriteLine("ERROR: Program returned the following error: {0}", error);

Displaying the returned account numbers

The List (L) function of the DATAFILE CICS program returns a space-delimited list of account numbers for accounts that have the premier flag set to true. Listing 5 shows you how to convert the COMMAREA into a string, split the string with space delimiters, and display each account number:

Listing 5. Displaying the account numbers
// Split account numbers into an array
string accountNos = CicsEncoding.GetString(outCommarea, 1, outCommarea.Length – 1);
string[] accounts = accountNos.Split(' ');

// Display returned account numbers
Console.WriteLine("Premier account numbers:");
foreach (string account in accounts) {
   Console.WriteLine("   {0}", account);

Completing the basic application

You can create the complete basic application by combining Listings 1 through 5 and displaying a program banner when the application starts. You can download the complete application code and Visual Studio project at the bottom of the article.

Data marshalling in .NET applications

This section shows you how to extend the basic .NET console application to create an intermediate application that connects to CICS and invokes the Retrieve (R) and Make Premier (P) functions in the DATAFILE CICS program. After calling the CICS program, the information is extracted from the returned COMMAREA and converted into .NET native types.

Byte ordering

The article mentioned that data conversion is often required for text fields in a COMMAREA. When binary fields are packed into or unpacked from a COMMAREA, you must take into account the byte ordering of the CICS server. The DATAFILE CICS program places a 4-byte integer field into the COMMAREA. If the CICS server is running on z/OS, the integer will be packed into the COMMAREA with the most significant byte first (big-endian). If the CICS server is running on an x86 or x64 architecture, the integer will be packed with the least significant byte first (little-endian). Figure 2 shows how a 4 byte integer is represented in big-endian and little-endian formats:

Figure 2. Byte ordering comparison
Byte ordering comparison
Byte ordering comparison

The sample .NET program includes a constant boolean field named BigEndian that controls the order in which bytes are packed. The default value is true, so it must be changed to false when the remote CICS server is running on a little-endian architecture.

Data marshalling between the .NET application and CICS

When sending a request to CICS, the local variables defined in the application must be packed into a byte array that will be sent in the COMMAREA. The request consists of a byte representing the operation to perform followed by a 6-digit account number to perform the operation on. The easiest way to build it is to build a string and use the existing logic in CallDatafileProgram to perform the conversion to a byte array.

When the CICS call has completed, the fields must be extracted from the COMMAREA returned from CICS and converted into .NET data types. To encapsulate this process, a new AccountRecord class has been created that represents an account record returned from the DATAFILE CICS program. The class needs to map out the layout of the COMMAREA byte array, which you can do by defining offsets and lengths represented in the byte array, as shown in Listing 6:

Listing 6. Constants for offsets and lengths of COMMAREA fields
// Constants for offsets and lengths of COMMAREA fields
private const int AccountNoOffset = 1;
private const int AccountNoLength = 6;
private const int SurnameOffset = 7;
private const int SurnameLength = 16;
private const int ForenameOffset = 23;
private const int ForenameLength = 16;
private const int BalanceOffset = 39;
private const int PremierOffset = 43;

You then define the local class variables, which are primitive .NET types, to represent an account record. Properties for retrieving and updating the variables are defined implicitly using C# automatic properties.

The constructor is responsible for populating the class variables. For the integer account number, the string is read using the CICS encoding, and then parsed as an integer. For the surname and forename fields, each field is created by extracting the bytes from the array starting at the appropriate offset and then converting to a .NET string using the CICS encoding. Finally, the string is trimmed to remove any trailing spaces or null characters.

For the balance, the 4-byte integer needs to be read in from the byte array and then converted to currency format by casting to a decimal. To combine the 4 bytes making up the integer, the value needs to be shifted left a byte at a time and then logically OR'd together. The order that the bytes are combined in depends on the byte ordering of the CICS system. Listing 7 shows how the fields are extracted from the COMMAREA:

Listing 7. Extracting the fields from the COMMAREA
// Get account number
string accountNoStr = CicsEncoding.GetString(byteArray, AccountNoOffset, AccountNoLength);
AccountNo = int.Parse(accountNoStr);

// Get surname and forename
Surname = CicsEncoding.GetString(byteArray, SurnameOffset, SurnameLength);
Surname = Surname.TrimEnd('\0', ' ');
Forename = CicsEncoding.GetString(byteArray, ForenameOffset, ForenameLength);
Forename = Forename.TrimEnd('\0', ' ');

// Get balance
int intBalance = 0;
if (BigEndian) {
   intBalance = (byteArray[BalanceOffset] << 24) |
                (byteArray[BalanceOffset + 1] << 16) |
                (byteArray[BalanceOffset + 2] << 8) |
                (byteArray[BalanceOffset + 3]);
} else {
   intBalance = (byteArray[BalanceOffset]) |
                (byteArray[BalanceOffset + 1] << 8) |
                (byteArray[BalanceOffset + 2] << 16) |
                (byteArray[BalanceOffset + 3] << 24);
Balance = (decimal) intBalance / 100;

// Get premier status
string premier = CicsEncoding.GetString(byteArray, PremierOffset, 1);
IsPremier = (premier == "Y");

Completing the intermediate application

You can download the complete application code and Visual Studio project files at the bottom of the article.

Sending account records

The Update (U) function of the DATAFILE program lets you update an account record with new details, which involves sending the new account data in the COMMAREA and requires additional code to pack the AccountRecord fields into a byte array. This operation is the inverse of the one described in the previous section and is implemented as a new method in AccountRecord called ToByteArray.

First, a destination byte array of the necessary size is created and then each field is copied into it. The account number is stored as its 6-digit text representation encoded in CicsEncoding. The same process is applied to the surname and forename fields. The account balance is stored as a 4-byte integer value. To pack a 4-byte integer into the byte array requires each of the bytes to be extracted from the integer via bit shifting and masking. Finally, the premier account flag is packed into the COMMAREA as a single byte set to either Y or N. Listing 8 shows how each field is packed into the byte array:

Listing 8. Converting an AccountRecord to a byte array
byte[] outputBytes = new byte[BytearrayLen];

// Set account number
string accountNoStr = AccountNo.ToString(“000000”);
byte[] tempBytes = CicsEncoding.GetBytes(accountNoStr);
Array.Copy(tempBytes, 0, outputBytes, AccountNoOffset, AccountNoLength);

// Set surname and forename
tempBytes = CicsEncoding.GetBytes(Surname);
Array.Copy(tempBytes, 0, outputBytes, SurnameOffset,
           Math.Min(tempBytes.Length, SurnameLength));
tempBytes = CicsEncoding.GetBytes(Forename);
Array.Copy(tempBytes, 0, outputBytes, ForenameOffset,
           Math.Min(tempBytes.Length, ForenameLength));

// Set balance
int intBalance = (int) (Balance * 100);
if (BigEndian) {
   outputBytes[BalanceOffset] = (byte) ((intBalance >> 24) & 0x000000ff);
   outputBytes[BalanceOffset + 1] = (byte) ((intBalance >> 16) & 0x000000ff);
   outputBytes[BalanceOffset + 2] = (byte) ((intBalance >> 8) & 0x000000ff);
   outputBytes[BalanceOffset + 3] = (byte) (intBalance & 0x000000ff);
} else {
   outputBytes[BalanceOffset] = (byte) (intBalance & 0x000000ff);
   outputBytes[BalanceOffset + 1] = (byte) ((intBalance >> 8) & 0x000000ff);
   outputBytes[BalanceOffset + 2] = (byte) ((intBalance >> 16) & 0x000000ff);
   outputBytes[BalanceOffset + 3] = (byte) ((intBalance >> 24) & 0x000000ff);

// Set premier flag
if (IsPremier) {
   outputBytes[PremierOffset] = CicsEncoding.GetBytes("Y")[0];
} else {
   outputBytes[PremierOffset] = CicsEncoding.GetBytes("N")[0];

Adding a graphical user interface

The advanced program extends the intermediate program by adding a GUI in place of console-based input and output. The GUI consists of a number of controls to facilitate retrieving, modifying, and updating customer account records, as shown in Figure 3. Table 1 describes each control:

Figure 3. Graphical user interface for the advanced sample
Graphical user interface for the advanced sample
Graphical user interface for the advanced sample
Table 1. Controls that make up the user interface
No.NameControl typePurpose
1accountNoSelectNumericUpDownHolds the desired account number
2displayButtonButtonDisplays the numbered account
3updateButtonButtonUpdates the numbered account
4accountInfoBoxGroupBoxContains the account details
5accountNoLabelLabelHolds the account number
6surnameBoxTextBoxHolds the surname
7forenameBoxTextBoxHolds the forename
8poundsBox, penceBoxTextBoxHolds the account balance
9premierCheckCheckBoxHolds the premier status
10statusLabelToolStripStatusLabelDisplays a status message

Thread separation

A primary concern when writing GUI-based applications is the responsiveness of the user interface. Actions such as button clicks occur on a user interface thread, and any time-consuming operations can prevent the interface from being redrawn or responding to further user actions. Therefore any non-trivial operations such as invoking a CICS program should be offloaded onto a separate thread in order to keep the user interface responsive.

The use of a GatewayConnection object is restricted to the thread that initially created it. In order to allow a GatewayConnection to be used by multiple threads, you must create a dedicated communications thread. Other threads can then send requests by placing them in a data structure such as a queue for the communications thread to process. When combined with the user interface thread, this arrangement results in an architecture like the one in Figure 4:

Figure 4. Architecture of the advanced sample
Architecture of the advanced sample
Architecture of the advanced sample

When the user performs an action that requires a CICS program to be invoked, the event handler on the UI thread passes control to a thread retrieved from the system thread pool. The UI thread can then return to updating the UI. The pooled thread constructs an EciRequest that will perform the desired action, and adds it to a queue. The pooled thread then waits on an event associated with the ECI request. The communications thread reads requests off the queue and flows them to CICS. Once the flow operation has completed (successfully or unsuccessfully), the communications thread signals the event. This wakes the pooled thread, which can then process the result of the ECI request and update the interface by invoking a method on the UI thread.

In this example, it is unlikely that there will be more than one request on the queue. However, you can use this design in other environments where multiple threads need to send requests to CICS, such as an ASP.NET application running inside Microsoft IIS.


This article showed you how to write a simple single-threaded C# application using Microsoft Visual Studio 2008 or 2010 to retrieve customer account data from a CICS server via a CICS communications area (COMMAREA). The application was then enhanced by adding serialization logic to pack and unpack .NET data types into and out of the COMMAREA, and by creating a GUI and separating presentation logic from business logic to allow all fields in a record to be easily updated.

Downloadable resources

Related topics


Sign in or register to add and subscribe to comments.

ArticleTitle=Accessing CICS from Microsoft .NET applications using CICS Transaction Gateway