VMS Tech Demo 20 - TIG (Transparent Interface Gateway)

Content:

  1. Introduction
  2. Scenario
  3. Process
  4. Pascal
    1. Starting point
    2. Create library
    3. Test library
    4. Create shareable image
    5. Create TIG definition and artifacts
    6. Test client
    7. Web application
  5. Basic
    1. Starting point
    2. Create library
    3. Test library
    4. Create shareable image
    5. Create TIG definition and artifacts
    6. Test client
    7. Web application
  6. Cobol
    1. Starting point
    2. Create library
    3. Test library
    4. Create shareable image
    5. Create TIG definition and artifacts
    6. Test client
    7. Web application

Introduction:

This is a demo of my little TIG (Transparent Interface Gateway) tool.

A brief description of TIG can be found here.

It is basically a tool that expose functionality residing in a VMS shareable image to clients running anywhere without having to manually write too much glue code.

Scenario:

The examples will use managing a very simple customer database (just id, name and address) as problem.

Way more simple than real world code, but sufficient to demonstrate how things work and easier to follow.

On the VMS side we will have business code in Pascal/Basic/Cobol.

On the client side (Windows/Linux/VMS) we will have clients in Java/Groovy/C#/Python/PHP.

Note that these client languages are just those that I have chosen to test with. The Java client can besides Java and Groovy of course also be used from Kotlin and Scala. TIG also support VB.NET, C and C++. The C client can with a little ingenuity be called from many other native languages.

The API exposed will have the methods:

The arguments for the methods in the exposed API will be almost the same for the 3 VMS languages:

Pascal Basic Cobol
id integer
(32 bit integer)
by reference
integer
(32 bit integer)
by reference
pic 9(7) display
(string length 7)
by reference
name varying [255] of char
(variable length string)
by reference
string
(dynamic fixed length string)
by descriptor
pic x(32)
(fixed length string)
by reference
customer
   namestr = packed array [1..NAME_LEN] of char;
   addrstr = packed array [1..ADDR_LEN] of char;
   customer = record
                 id : [key(0)] integer;
                 name : [key(1)] namestr;
                 address : addrstr;
              end;
by reference
record customer
    integer id
    string xname = 32
    string address = 64
end record
by reference
01 customer-record.
    03 customer-id pic 9(7) display.
    03 customer-name pic x(32).
    03 customer-address pic x(64).
by reference

The types are how I imagine a traditional VMS developer in the respective languages would create the API.

Some caveats:

*) This could be solved by changing the API in a way so that all interactions would be single call, but that is out of scope for this article.

Disclaimer: I have little experience in VMS Basic and Cobol so the Basic and Cobol code may have a few "unusual" ways of of doing things.

Process:

As a starting point we have:

We will do the following:

  1. Extract the code to be exposed as a separate library.
  2. Create a server side test program to test the library. The main program is a little cumbersome to use for testing.
  3. Create a shareable image from the library. To do this we create new wrapper routines that expose a more standardized API including always returning a status value as function result.
  4. Create a XML description of the API and generate both a client stub and server skeleton from that.
  5. Create a client side test program similar to the server side test program.
  6. Do a real web application instead of the command line stuff.

We will only see Java and PHP web applications demoed. I is obvious possible to create web applications in Groovy, C# and Python. But this is about TIG not about writing web applications - and I can easily write Java and PHP web applications in simple year 2000 style. Doing Grails, ASP.NET Core MVC and Django is certainly doable but would add unwanted distractions (so would doing Spring MVC and Larevel).

More info about web applications and modern web frameworks can be found here:

Now it is time to jump to your language of preference:

Pascal:

Setup:

Pascal

Starting point:

p.inc:

const
   NAME_LEN = 32;
   ADDR_LEN = 64;
   ISQ_FNM = 'p.isq';

type
   namestr = packed array [1..NAME_LEN] of char;
   addrstr = packed array [1..ADDR_LEN] of char;
   customer = record
                 id : [key(0)] integer;
                 name : [key(1)] namestr;
                 address : addrstr;
              end;

pload.pas:

program pload(input,output);

%include 'p.inc'

var
   cf : file of customer;
   c : customer;

begin
   open(cf, ISQ_FNM, new, organization := indexed, access_method := keyed);
   rewrite(cf);
   c.id := 1;
   c.name := 'A A';
   c.address := 'A road 1';
   cf^ := c;
   put(cf);
   c.id := 2;
   c.name := 'B B';
   c.address := 'B road 2';
   cf^ := c;
   put(cf);
   c.id := 3;
   c.name := 'C C';
   c.address := 'C road 3';
   cf^ := c;
   put(cf);
   close(cf);
end.

pmain.pas:

program pmain(input,output);

%include 'p.inc'

var
   cf : file of customer;

procedure open_file;

begin
   open(cf, ISQ_FNM, old, organization := indexed, access_method := keyed);   
   reset(cf);
end;

function find_by_id(id : integer; var c : customer) : boolean;

begin
   findk(cf, 0, id);
   if not ufb(cf) then begin
      c := cf^;
      find_by_id := true;
   end else begin
      find_by_id := false;
   end;
end;

function find_by_name(name : namestr; var c : customer) : boolean;

begin
   findk(cf, 1, name);
   if not ufb(cf) then begin
      c := cf^;
      find_by_name := true;
   end else begin
      find_by_name := false;
   end;
end;

procedure list_start;

begin
   reset(cf);
end;

function list_next(var c : customer) : boolean;

begin
   if not eof(cf) then begin
      c := cf^;
      get(cf);
      list_next := true;
   end else begin
      list_next := false;
   end;
end;

function add(c : customer) : boolean;

var
   c2 : customer;

begin
   if (c.id > 0) and (c.name <> '') and not find_by_id(c.id, c2) and not find_by_name(c.name, c2) then begin
      cf^ := c;
      put(cf);
      add := true;
   end else begin
      add := false;
   end;
end;

procedure close_file;

begin
   close(cf);
end;

procedure dump(c : customer);

begin
   writeln('id=', c.id:1);
   writeln('name=', c.name);
   writeln('address=', c.address);
end;

var
   done : boolean;
   sel : char;
   id : integer;
   name : namestr;
   c : customer;

begin
   open_file;
   done := false;
   while not done do begin
      writeln('Options:');
      writeln('  1) Lookup by id');
      writeln('  2) Lookup by name');
      writeln('  3) List all');
      writeln('  4) Add');
      writeln('  5) Exit');
      write('Choice: ');
      readln(sel);
      case(sel) of
         '1' : begin
                  write('Enter id: ');
                  readln(id);
                  if find_by_id(id, c) then begin
                     writeln('find by id: ', id:1);
                     dump(c);
                  end else begin
                     writeln('not found: ', id:1);
                  end;
               end;
         '2' : begin
                  write('Enter name: ');
                  readln(name);
                  if find_by_name(name, c) then begin
                     writeln('find by name: ', name);
                     dump(c);
                  end else begin
                     writeln('not found: ', name);
                  end;
               end;
         '3' : begin
                  writeln('list');
                  list_start;
                  while list_next(c) do begin
                     dump(c);
                  end;
               end;
         '4' : begin
                  write('Enter id: ');
                  readln(c.id);
                  write('Enter name: ');
                  readln(c.name);
                  write('Enter address: ');
                  readln(c.address);
                  if add(c) then begin
                     writeln('add succeeded');
                  end else begin
                     writeln('add failed');
                  end;
               end;
         '5' : begin
                  done := true;
               end;
         otherwise begin
                      (* nothing *)
                   end;
      end;
   end;
   close_file;
end.

Create library:

p.inc:

const
   NAME_LEN = 32;
   ADDR_LEN = 64;
   ISQ_FNM = 'p.isq';

type
   varstr = varying [255] of char;
   namestr = packed array [1..NAME_LEN] of char;
   addrstr = packed array [1..ADDR_LEN] of char;
   customer = record
                 id : [key(0)] integer;
                 name : [key(1)] namestr;
                 address : addrstr;
              end;

plib.pas:

module pcore(input,output);

%include 'p.inc'

var
   cf : file of customer;

procedure open_file;

begin
   open(cf, ISQ_FNM, old, organization := indexed, access_method := keyed);   
   reset(cf);
end;

function find_by_id(id : integer; var c : customer) : boolean;

begin
   findk(cf, 0, id);
   if not ufb(cf) then begin
      c := cf^;
      find_by_id := true;
   end else begin
      find_by_id := false;
   end;
end;

function find_by_name(name : namestr; var c : customer) : boolean;

begin
   findk(cf, 1, name);
   if not ufb(cf) then begin
      c := cf^;
      find_by_name := true;
   end else begin
      find_by_name := false;
   end;
end;

procedure list_start;

begin
   reset(cf);
end;

function list_next(var c : customer) : boolean;

begin
   if not eof(cf) then begin
      c := cf^;
      get(cf);
      list_next := true;
   end else begin
      list_next := false;
   end;
end;

function add(c : customer) : boolean;

var
   c2 : customer;

begin
   if (c.id > 0) and (c.name <> '') and not find_by_id(c.id, c2) and not find_by_name(c.name, c2) then begin
      cf^ := c;
      put(cf);
      add := true;
   end else begin
      add := false;
   end;
end;

procedure close_file;

begin
   close(cf);
end;

end.

pmain.pas:

[inherit('plib')]
program pmain(input,output);

procedure dump(c : customer);

begin
   writeln('id=', c.id:1);
   writeln('name=', c.name);
   writeln('address=', c.address);
end;

var
   done : boolean;
   sel : char;
   id : integer;
   name : namestr;
   c : customer;

begin
   open_file;
   done := false;
   while not done do begin
      writeln('Options:');
      writeln('  1) Lookup by id');
      writeln('  2) Lookup by name');
      writeln('  3) List all');
      writeln('  4) Add');
      writeln('  5) Exit');
      write('Choice: ');
      readln(sel);
      case(sel) of
         '1' : begin
                  write('Enter id: ');
                  readln(id);
                  if find_by_id(id, c) then begin
                     writeln('find by id: ', id:1);
                     dump(c);
                  end else begin
                     writeln('not found: ', id:1);
                  end;
               end;
         '2' : begin
                  write('Enter name: ');
                  readln(name);
                  if find_by_name(name, c) then begin
                     writeln('find by name: ', name);
                     dump(c);
                  end else begin
                     writeln('not found: ', name);
                  end;
               end;
         '3' : begin
                  writeln('list');
                  list_start;
                  while list_next(c) do begin
                     dump(c);
                  end;
               end;
         '4' : begin
                  write('Enter id: ');
                  readln(c.id);
                  write('Enter name: ');
                  readln(c.name);
                  write('Enter address: ');
                  readln(c.address);
                  if add(c) then begin
                     writeln('add succeeded');
                  end else begin
                     writeln('add failed');
                  end;
               end;
         '5' : begin
                  done := true;
               end;
         otherwise begin
                      (* nothing *)
                   end;
      end;
   end;
   close_file;
end.

Build:

$ pas/env plib
$ pas pmain
$ link pmain + plib

Test library:

ptest.pas:

[inherit('plib')]
program pmain(input,output);

procedure dump(c : customer);

begin
   writeln('id=', c.id:1);
   writeln('name=', c.name);
   writeln('address=', c.address);
end;

procedure test_find_by_id(id : integer);

var
   c : customer;

begin
   if find_by_id(id, c) then begin
      writeln('find by id: ', id:1);
      dump(c);
   end else begin
      writeln('not found: ', id:1);
   end;
end;

procedure test_find_by_name(name : varstr);

var
   c : customer;

begin
   if find_by_name(name, c) then begin
      writeln('find by name: ', name);
      dump(c);
   end else begin
      writeln('not found: ', name);
   end;
end;

procedure test_list;

var
   c : customer;

begin
   writeln('list');
   list_start;
   while list_next(c) do begin
      dump(c);
   end;
end;

procedure test_add(id : integer; name : varstr; addr : varstr);

var
   c : customer;

begin
   c.id := id;
   c.name := name;
   c.address := addr;
   if add(c) then begin
      writeln('add succeeded');
   end else begin
      writeln('add failed');
   end;
end;

begin
   open_file;
   test_find_by_id(2);
   test_find_by_id(5);
   test_find_by_name('B B');
   test_find_by_name('E E');
   test_list;
   test_add(5, 'E E', 'E road 5');
   test_list;
   test_add(0, 'X X', 'X road');
   test_list;
   close_file;
end.

Build and run:

$ pas ptest
$ link ptest + plib
$ run ptest

Create shareable image:

pwrap.pas:

[inherit('plib','sys$library:starlet')]
module pwrap(input,output);

[global]
function p_open_file : integer;

begin
   open_file;
   p_open_file := SS$_NORMAL;
end;

[global]
function p_find_by_id(id : integer; var c : customer) : integer;

begin
   if find_by_id(id, c) then begin
      p_find_by_id := SS$_NORMAL;
   end else begin
      p_find_by_id := SS$_ABORT;
   end;
end;

[global]
function p_find_by_name(name : varstr; var c : customer) : integer;

begin
   if find_by_name(pad(name, ' ', NAME_LEN), c) then begin
      p_find_by_name := SS$_NORMAL;
   end else begin
      p_find_by_name := SS$_ABORT;
   end;
end;

[global]
function p_list_start : integer;

begin
   list_start;
   p_list_start := SS$_NORMAL;
end;

[global]
function p_list_next(var c : customer) : integer;

begin
   if list_next(c) then begin
      p_list_next := SS$_NORMAL;
   end else begin
      p_list_next := SS$_ABORT;
   end;
end;

[global]
function p_add(c : customer) : integer;

begin
   if add(c) then begin
      p_add := SS$_NORMAL;
   end else begin
      p_add := SS$_ABORT;
   end;
end;

[global]
function p_close_file : integer;

begin
   close_file;
   p_close_file := SS$_NORMAL;
end;

end.

Build:

$ pas pwrap
$ link/share=pshr pwrap + plib + sys$input/opt
symbol_vector=(p_open_file=procedure,-
               p_find_by_id=procedure,-
               p_find_by_name=procedure,-
               p_list_start=procedure,-
               p_list_next=procedure,-
               p_add=procedure,-
               p_close_file=procedure)
$
$ define/nolog pshr "''f$parse("pshr.exe")'"

Create TIG definition and artifacts:

p.xml:

<config>
    <image>pshr</image>
    <port>30001</port>
    <mt>false</mt>
    <server>
        <language>dk.vajhoej.vms.tig.server.JavaServerGen</language>
    </server>
    <client>
        <language>dk.vajhoej.vms.tig.client.JavaClientGen</language>
        <language>dk.vajhoej.vms.tig.client.CSharpClientGen</language>
        <language>dk.vajhoej.vms.tig.client.PyClientGen</language>
        <language>dk.vajhoej.vms.tig.client.PhpClientGen</language>
    </client>
    <methods>
        <method>
            <name>p_open_file</name>
            <args/>
        </method>
        <method>
            <name>p_find_by_id</name>
            <args>
                <arg>
                    <name>id</name>
                    <type>LongWord</type>
                    <pass>Reference</pass>
                    <use>In</use>
                </arg>
                <arg>
                    <name>c</name>
                    <type>Block</type>
                    <impl>PCustomer</impl>
                    <pass>Reference</pass>
                    <use>InOut</use>
                </arg>
            </args>
        </method>
        <method>
            <name>p_find_by_name</name>
            <args>
                <arg>
                    <name>name</name>
                    <type>VariableCharacterString</type>
                    <pass>Reference</pass>
                    <use>In</use>
                </arg>
                <arg>
                    <name>c</name>
                    <type>Block</type>
                    <impl>PCustomer</impl>
                    <pass>Reference</pass>
                    <use>InOut</use>
                </arg>
            </args>
        </method>
        <method>
            <name>p_list_start</name>
            <args/>
        </method>
        <method>
            <name>p_list_next</name>
            <args>
                <arg>
                    <name>c</name>
                    <type>Block</type>
                    <impl>PCustomer</impl>
                    <pass>Reference</pass>
                    <use>InOut</use>
                </arg>
            </args>
        </method>
        <method>
            <name>p_add</name>
            <args>
                <arg>
                    <name>c</name>
                    <type>Block</type>
                    <impl>PCustomer</impl>
                    <pass>Reference</pass>
                    <use>In</use>
                </arg>
            </args>
        </method>
        <method>
            <name>p_close_file</name>
            <args/>
        </method>
    </methods>
</config>

Gen:

java -cp vmstig.jar dk.vajhoej.vms.tig.Gen p.xml server client

Run server:

$ javac -cp vmstig.jar:vmscall.jar:record.jar PshrServer.java
$ java -cp .:vmstig.jar:vmscall.jar:record.jar "PshrServer"

Test client:

PCustomer.java:

import dk.vajhoej.record.FieldType;
import dk.vajhoej.record.Struct;
import dk.vajhoej.record.StructField;

@Struct
public class PCustomer {
    @StructField(n=0,type=FieldType.INT4)
    private int id;
    @StructField(n=1,type=FieldType.FIXSTR,length=32,pad=true,padchar=' ')
    private String name;
    @StructField(n=2,type=FieldType.FIXSTR,length=64,pad=true,padchar=' ')
    private String address;
    public PCustomer() {
        this(0, "", "");
    }
    public PCustomer(int id, String name, String address) {
        this.id = id;
        this.name = name;
        this.address = address;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
}

PTest.java:

import java.io.IOException; 

import dk.vajhoej.record.RecordException;

public class PTest {
    private static void dump(PCustomer c) {
        System.out.println("id=" + c.getId());
        System.out.println("name=" + c.getName());
        System.out.println("address=" + c.getAddress());
    }
    private static void testFindById(PshrClient cli, int id) throws IOException, RecordException {
        PCustomer[] c = new PCustomer[1];
        c[0] = new PCustomer();
        if(cli.p_find_by_id(id, c) % 2 == 1) {
            System.out.println("find by id: " + id);
            dump(c[0]);
        } else {
            System.out.println("not found: " + id);
        }
    }
    private static void testFindByName(PshrClient cli, String name) throws IOException, RecordException {
        PCustomer[] c = new PCustomer[1];
        c[0] = new PCustomer();
        if(cli.p_find_by_name(name, c) % 2 == 1) {
            System.out.println("find by name: " + name);
            dump(c[0]);
        } else {
            System.out.println("not found: " + name);
        }
    }
    private static void testList(PshrClient cli) throws IOException, RecordException {
        System.out.println("list");
        cli.p_list_start();
        PCustomer[] c = new PCustomer[1];
        c[0] = new PCustomer();
        while(cli.p_list_next(c) % 2 == 1) {
            dump(c[0]);
        }
    }
    private static void testAdd(PshrClient cli, int id, String name, String address) throws IOException, RecordException {
        PCustomer c = new PCustomer(id, name, address);
        if(cli.p_add(c) %2 == 1) {
            System.out.println("add succeeded");
        } else {
            System.out.println("add failed");
        }
    }
    public static void main(String[] args) throws IOException, RecordException {
        PshrClient cli = new PshrClient("arne4");
        cli.p_open_file();
        testFindById(cli, 2);
        testFindById(cli, 6);
        testFindByName(cli, "B B");
        testFindByName(cli, "F F");
        testList(cli);
        testAdd(cli, 6, "F F", "F road 6");
        testList(cli);
        testAdd(cli, 0, "X X", "X road");
        testList(cli);
        cli.p_close_file();
    }
}

Build and run:

javac -cp record.jar;vmstig.jar PCustomer.java PshrClient.java PTest.java
java -cp .;record.jar;vmstig.jar PTest

PCustomer.java:

import dk.vajhoej.record.FieldType;
import dk.vajhoej.record.Struct;
import dk.vajhoej.record.StructField;

@Struct
public class PCustomer {
    @StructField(n=0,type=FieldType.INT4)
    private int id;
    @StructField(n=1,type=FieldType.FIXSTR,length=32,pad=true,padchar=' ')
    private String name;
    @StructField(n=2,type=FieldType.FIXSTR,length=64,pad=true,padchar=' ')
    private String address;
    public PCustomer() {
        this(0, "", "");
    }
    public PCustomer(int id, String name, String address) {
        this.id = id;
        this.name = name;
        this.address = address;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
}

PTest.groovy:

def dump(c) {
    println("id=" + c.id)
    println("name=" + c.name)
    println("address=" + c.address)
}

def testFindById(cli, id) {
    c = new PCustomer[1]
    c[0] = new PCustomer()
    if(cli.p_find_by_id(id, c) % 2 == 1) {
        println("find by id: " + id)
        dump(c[0])
    } else {
        println("not found: " + id)
    }
}

def testFindByName(cli, name) {
    c = new PCustomer[1]
    c[0] = new PCustomer()
    if(cli.p_find_by_name(name, c) % 2 == 1) {
        println("find by name: " + name)
        dump(c[0])
    } else {
        println("not found: " + name)
    }
}

def testList(cli) {
    println("list")
    cli.p_list_start()
    c = new PCustomer[1]
    c[0] = new PCustomer()
    while(cli.p_list_next(c) % 2 == 1) {
        dump(c[0])
    }
}
def testAdd(cli, id, name, address) {
    c = new PCustomer(id, name, address)
    if(cli.p_add(c) %2 == 1) {
        println("add succeeded")
    } else {
        println("add failed")
    }
}

cli = new PshrClient("arne4")
cli.p_open_file()
testFindById(cli, 2)
testFindById(cli, 7)
testFindByName(cli, "B B")
testFindByName(cli, "G G")
testList(cli)
testAdd(cli, 7, "G G", "G road 7")
testList(cli)
testAdd(cli, 0, "X X", "X road")
testList(cli)
cli.p_close_file()

Build and run:

javac -cp record.jar;vmstig.jar PCustomer.java PshrClient.java
set classpath=.;record.jar;vmstig.jar
groovy PTest.groovy

PCustomer.cs:

using Vajhoej.Record;

[Struct]
public class PCustomer
{
	[StructField(N=0, Type=FieldType.INT4)]
	private int id;
	[StructField(N=1, Type=FieldType.FIXSTR, Length=32, Pad=true, PadChar=' ')]
	private string name;
	[StructField(N=2, Type=FieldType.FIXSTR, Length=64, Pad=true, PadChar=' ')]
	private string address;
	public PCustomer() : this(0, "", "")
	{
	}
	public PCustomer(int id, string name, string address)
	{
	    this.id = id;
	    this.name = name;
	    this.address = address;
	}
	public int Id
	{
		get { return id; }
		set { id = value; }
	}
	public string Name
	{
		get { return name; }
		set { name = value; }
	}
	public string Address
	{
		get { return address; }
		set { address = value; }
	}
}

PTest.cs:

using System;

public class PTest
{
    private static void Dump(PCustomer c)
    {
        Console.WriteLine("id=" + c.Id);
        Console.WriteLine("name=" + c.Name);
        Console.WriteLine("address=" + c.Address);
    }
    private static void TestFindById(PshrClient cli, int id)
    {
        PCustomer c = new PCustomer();
        if(cli.P_find_by_id(id, ref c) % 2 == 1)
        {
            Console.WriteLine("find by id: " + id);
            Dump(c);
        }
        else
        {
            Console.WriteLine("not found: " + id);
        }
    }
    private static void TestFindByName(PshrClient cli, string name)
    {
        PCustomer c = new PCustomer();
        if(cli.P_find_by_name(name, ref c) % 2 == 1)
        {
            Console.WriteLine("find by name: " + name);
            Dump(c);
        }
        else
        {
            Console.WriteLine("not found: " + name);
        }
    }
    private static void TestList(PshrClient cli)
    {
        Console.WriteLine("list");
        cli.P_list_start();
        PCustomer c = new PCustomer();
        while(cli.P_list_next(ref c) % 2 == 1)
        {
            Dump(c);
        }
    }
    private static void TestAdd(PshrClient cli, int id, String name, String address)
    {
        PCustomer c = new PCustomer(id, name, address);
        if(cli.P_add(c) %2 == 1)
        {
            Console.WriteLine("add succeeded");
        }
        else
        {
            Console.WriteLine("add failed");
        }
    }
    public static void Main(string[] args)
    {
        PshrClient cli = new PshrClient("ARNE4");
        cli.P_open_file();
        TestFindById(cli, 2);
        TestFindById(cli, 8);
        TestFindByName(cli, "B B");
        TestFindByName(cli, "H H");
        TestList(cli);
        TestAdd(cli, 8, "H H", "H road 8");
        TestList(cli);
        TestAdd(cli, 0, "X X", "X road");
        TestList(cli);
        cli.P_close_file();
    }
}

Build and run:

csc /r:tig.dll /r:record.dll PTest.cs PshrClient.cs PCustomer.cs
PTest

PCustomer.py:

import struct

class PCustomer:
    def __init__(self, id = 0, name = '', address = ''):
        self.id = id
        self.name = name
        self.address = address
    def pack(self):
        return struct.pack('<l32s64s', self.id, bytes(self.name.ljust(32,' '), 'iso-8859-1'), bytes(self.address.ljust(64,' '), 'iso-8859-1'))
    def unpack(self, blk):
        data = struct.unpack('<l32s64s', blk)
        self.id = data[0]
        self.name = str(data[1], 'iso-8859-1').rstrip(' \0')
        self.address = str(data[2], 'iso-8859-1').rstrip(' \0')

ptest.py:

from PshrClient import PshrClient

from PCustomer import PCustomer

def dump(c):
    print('id=' + str(c.id))
    print('name=' + c.name)
    print('address=' + c.address)

def test_find_by_id(cli, id):
    c = PCustomer()
    stat, c = cli.p_find_by_id(id, c)
    if stat % 2 == 1:
        print('find by id: ' + str(id))
        dump(c)
    else:
        print('not found: ' + str(id))

def test_find_by_name(cli, name):
    c = PCustomer()
    stat, c = cli.p_find_by_name(name, c)
    if stat % 2 == 1:
        print('find by name: ' + name)
        dump(c)
    else:
        print('not found: ' + name)

def test_list(cli):
    print('list')
    cli.p_list_start()
    while True:
        c = PCustomer()
        stat, c = cli.p_list_next(c)
        if stat % 2 != 1: break
        dump(c)

def test_add(cli, id, name, address):
    c = PCustomer(id, name, address)
    if cli.p_add(c) % 2 == 1:
        print('add succeded')
    else:
        print('add failed')
                                                                                                                                              
cli = PshrClient('arne4')
cli.p_open_file()
test_find_by_id(cli, 2)
test_find_by_id(cli, 9)
test_find_by_name(cli, 'B B')
test_find_by_name(cli, 'I I')
test_list(cli)
test_add(cli, 9, 'I I', 'I road 9')
test_list(cli)
test_add(cli, 0, 'X X', 'X road')
test_list(cli)
cli.p_close_file()

Run:

python ptest.py

PCustomer.php:

<?php

class PCustomer {
    public $id;
    public $name;
    public $address;
    function __construct($id = 0, $name = '', $address = '') {
        $this->id = $id;
        $this->name = $name;
        $this->address = $address;
    }
    function pack() {
        return pack('Va32a64', $this->id, str_pad($this->name, 32, ' '), str_pad($this->address, 64, ' '));
    }
    function unpack($blk) {
        $this->id = unpack('V', $blk, 0)[1];
        $this->name = trim(unpack('a32', $blk, 4)[1]);
        $this->address = trim(unpack('a64', $blk, 36)[1]);
    }
}

?>

ptest.php:

<?php

include 'PCustomer.php';
include 'PshrClient.php';

function dump($c) {
    echo 'id=' . $c->id . "\r\n";
    echo 'name=' . $c->name . "\r\n";
    echo 'address=' . $c->address . "\r\n";
}

function test_find_by_id($cli, $id) {
    $c = new PCustomer();
    if($cli->p_find_by_id($id, $c) % 2 == 1) {
        echo 'find by id: ' . $id . "\r\n";
        dump($c);
    } else {
        echo 'not found: ' . $id . "\r\n";
    }
}

function test_find_by_name($cli, $name) {
    $c = new PCustomer();
    if($cli->p_find_by_name($name, $c) % 2 == 1) {
        echo 'find by name: ' . $name . "\r\n";
        dump($c);
    } else {
        echo 'not found: ' . $name . "\r\n";
    }
}

function test_list($cli) {
    echo "list\r\n";
    $cli->p_list_start();
    $c = new PCustomer();
    while($cli->p_list_next($c) % 2 == 1) {
        dump($c);
    }
}

function test_add($cli, $id, $name, $address) {
    $c = new PCustomer($id, $name, $address);
    if($cli->p_add($c) % 2 == 1) {
        echo "add succeeded\r\n";
    } else {
        echo "add failed\r\n";
    }
}

$cli = new PshrClient('arne4');
$cli->p_open_file();
test_find_by_id($cli, 2);
test_find_by_id($cli, 10);
test_find_by_name($cli, 'B B');
test_find_by_name($cli, 'J J');
test_list($cli);
test_add($cli, 10, 'J J', 'J road 10');
test_list($cli);
test_add($cli, 0, 'X X', 'X road');
test_list($cli);
$cli->p_close_file()

?>

Run:

php ptest.php

Web application:

p.jsp:

<html>
<head>
<title>Pshr test</title>
</head>
<body>
<h1>Pshr test</h1>
<h2>Find by id:</h2>
<form method="post" action="p_find_by_id.jsp">
Id: <input type="text" name="id">
<br>
<input type="submit" value="Find">
</form>
<h2>Find by name:</h2>
<form method="post" action="p_find_by_name.jsp">
Name: <input type="text" name="name">
<br>
<input type="submit" value="Find">
</form>
<h2>List all:</h2>
<form method="post" action="p_list.jsp">
<input type="submit" value="List">
</form>                                                                                                                         
<h2>Add one:</h2>
<form method="post" action="p_add.jsp">
Id: <input type="text" name="id">
<br>
Name: <input type="text" name="name">
<br>
Address: <input type="text" name="address">
<br>
<input type="submit" value="Add">
</form>
</body>
</html>

p_find_by_id.jsp:

<%@ page import="pshr.*" %>
<html>
<head>
<title>Find by id</title>
</head>
<body>
<h1>Find by id</h1>
<h2>Result:</h2>
<%
int id = Integer.parseInt(request.getParameter("id"));
synchronized(PshrClient.class) {
    PshrClient cli = new PshrClient("arne4");
    cli.p_open_file();
    PCustomer[] c = new PCustomer[1];
    c[0] = new PCustomer();
    if(cli.p_find_by_id(id, c) % 2 == 1) {
        out.println("id = " + c[0].getId() + "<br>");
        out.println("name = " + c[0].getName() + "<br>");
        out.println("address = " + c[0].getAddress());
    } else {
        out.println("Error: id " + id + " not found");
    }
    cli.p_close_file();
}
%>
</body>
</html>

p_find_by_name.jsp:

<%@ page import="pshr.*" %>
<html>
<head>
<title>Find by name</title>
</head>
<body>
<h1>Find by name</h1>
<h2>Result:</h2>
<%
String name = request.getParameter("name");
synchronized(PshrClient.class) {
    PshrClient cli = new PshrClient("arne4");
    cli.p_open_file();
    PCustomer[] c = new PCustomer[1];
    c[0] = new PCustomer();
    if(cli.p_find_by_name(name, c) % 2 == 1) {
        out.println("id = " + c[0].getId() + "<br>");
        out.println("name = " + c[0].getName() + "<br>");
        out.println("address = " + c[0].getAddress());
    } else {
        out.println("Error: name " + name + " not found");
    }
    cli.p_close_file();
}
%>
</body>
</html>

p_list.jsp:

<%@ page import="pshr.*" %>
<html>
<head>
<title>List all</title>
</head>
<body>
<h1>List all</h1>
<h2>List:</h2>
<table border="1">
<%
synchronized(PshrClient.class) {
    PshrClient cli = new PshrClient("arne4");
    cli.p_open_file();
    cli.p_list_start();
    PCustomer[] c = new PCustomer[1];
    c[0] = new PCustomer();
    while(cli.p_list_next(c) % 2 == 1) {
        out.println("<tr>");
        out.println("<td>" + c[0].getId() + "</td>");
        out.println("<td>" + c[0].getName() + "</td>");
        out.println("<td>" + c[0].getAddress() + "</td>");
        out.println("</tr>");
    }
    cli.p_close_file();
}
%>
</table>
</body>
</html>

p_add.jsp:

<%@ page import="pshr.*" %>
<html>
<head>
<title>Add one</title>
</head>
<body>
<h1>Add one</h1>
<h2>Status:</h2>
<%
int id = Integer.parseInt(request.getParameter("id"));
String name = request.getParameter("name");
String address = request.getParameter("address");
synchronized(PshrClient.class) {
    PshrClient cli = new PshrClient("arne4");
    cli.p_open_file();
    PCustomer c = new PCustomer(id, name, address);
    if(cli.p_add(c) % 2 == 1) {
        out.println(id + " added");
    } else {
        out.println(id + " not added");
    }
    cli.p_close_file();
}
%>
</body>
</html>

p.php:

<html>
<head>
<title>Pshr test</title>
</head>
<body>
<h1>Pshr test</h1>
<h2>Find by id:</h2>
<form method="post" action="p_find_by_id.php">
Id: <input type="text" name="id">
<br>
<input type="submit" value="Find">
</form>
<h2>Find by name:</h2>
<form method="post" action="p_find_by_name.php">
Name: <input type="text" name="name">
<br>
<input type="submit" value="Find">
</form>
<h2>List all:</h2>
<form method="post" action="p_list.php">
<input type="submit" value="List">
</form>
<h2>Add one:</h2>
<form method="post" action="p_add.php">
Id: <input type="text" name="id">
<br>
Name: <input type="text" name="name">
<br>
Address: <input type="text" name="address">
<br>
<input type="submit" value="Add">
</form>
</body>
</html>

p_find_by_id.php:

<html>
<head>
<title>Find by id</title>
</head>
<body>
<h1>Find by id</h1>
<h2>Result:</h2>
<?php

include 'PCustomer.php';
include 'PshrClient.php';

$id = (int)$_POST['id'];
$sem = sem_get(30001);
sem_acquire($sem);
$cli = new PshrClient('arne4');
$cli->p_open_file();
$c = new PCustomer();
if($cli->p_find_by_id($id, $c) % 2 == 1) {
    echo 'id = ' . $c->id . "<br>";
    echo 'name = ' . $c->name . "<br>";
    echo 'address = ' . $c->address . "";
} else {
    echo "Error: id $id not found";
}
$cli->p_close_file();
sem_release($sem);

?>
</body>
</html>

p_find_by_name.php:

<html>
<head>
<title>Find by name</title>
</head>
<body>
<h1>Find by name</h1>
<h2>Result:</h2>
<?php

include 'PCustomer.php';
include 'PshrClient.php';

$name = $_POST['name'];
$cli = new PshrClient('arne4');
$sem = sem_get(30001);
sem_acquire($sem);
$cli->p_open_file();
$c = new PCustomer();
if($cli->p_find_by_name($name, $c) % 2 == 1) {
    echo 'id = ' . $c->id . "<br>";
    echo 'name = ' . $c->name . "<br>";
    echo 'address = ' . $c->address . "";
} else {
    echo "Error: name $name not found";
}
$cli->p_close_file();
sem_release($sem);

?>
</body>
</html>

p_list.php:

<html>
<head>
<title>List all</title>
</head>
<body>
<h1>List all</h1>
<h2>List:</h2>
<table border="1">
<?php

include 'PCustomer.php';
include 'PshrClient.php';

$sem = sem_get(30001);
sem_acquire($sem);
$cli = new PshrClient('arne4');
$cli->p_open_file();
$cli->p_list_start();
$c = new PCustomer();
while($cli->p_list_next($c) % 2 == 1) {
    echo '<tr>';
    echo '<td>' . $c->id . '</td>';
    echo '<td>' . $c->name . '</td>';
    echo '<td>' . $c->address . '</td>';
    echo '</tr>';
}
$cli->p_close_file();
sem_release($sem);

?>
</table>
</body>
</html>

p_add.php:

<html>
<head>
<title>Add one</title>
</head>
<body>
<h1>Add one</h1>
<h2>Status:</h2>
<?php

include 'PCustomer.php';
include 'PshrClient.php';

$id = (int)$_POST['id'];
$name = $_POST['name'];
$address = $_POST['address'];
$sem = sem_get(30001);
sem_acquire($sem);
$cli = new PshrClient('arne4');
$cli->p_open_file();
$c = new PCustomer($id, $name, $address);
if($cli->p_add($c) % 2 == 1) {
    echo "$id added";
} else {
    echo "$id not added";
}
$cli->p_close_file();
sem_release($sem);

?>
</body>
</html>

Basic:

Setup:

Basic

Starting point:

b.inc:

declare integer constant TRUE = -1
declare integer constant FALSE = 0

record customer
    integer id
    string xname = 32
    string address = 64
end record

bload.bas:

program bload

option type = explicit

%include "b.inc"

map (cmap) customer cbuf

open "b.isq" as file #1, indexed fixed, recordtype none, map cmap, primary key cbuf::id, alternate key cbuf::xname
cbuf::id = 1
cbuf::xname = "A A"
cbuf::address = "A road 1"
put #1
cbuf::id = 2
cbuf::xname = "B B"
cbuf::address = "B road 2"
put #1
cbuf::id = 3
cbuf::xname = "C C"
cbuf::address = "C road 3"
put #1
close #1

end program

bmain.bas:

program bmain

option type = explicit

%include "b.inc"

map (cmap) customer cbuf

declare integer done, id
declare string sel, xname
declare customer c

external sub open_file
external integer function find_by_id(integer, customer)
external integer function find_by_name(string, customer)
external sub list_start
external integer function list_next(customer)
external integer function add(customer)
external sub close_file
external sub dump(customer)

call open_file
set no prompt
done = FALSE
while not done
    print "Options:"
    print "  1) Lookup by id"
    print "  2) Lookup by name"
    print "  3) List all"
    print "  4) Add"
    print "  5) Exit"
    input "Choice: "; sel
    select sel
        case = "1"
            input "Id: "; id
            if find_by_id(id, c) then
                print using "find by id: #", id
                call dump(c)
            else
                print using "not found: #", id
            end if
        case = "2"
            input "Name: "; xname 
            if find_by_name(xname, c) then
                print using "find by name: 'E", xname
                call dump(c)
            else
                print using "not found: 'E", xname
            end if
        case = "3"
            print "list"
            call list_start
            while list_next(c)
                call dump(c)
            next
        case = "4"
            input "Enter id: "; c::id
            input "Enter name: "; c::xname
            input "Enter address: "; c::address
            if add(c) then
                print "add succeeded"
            else
                print "add failed"
            end if
        case = "5"
            done = TRUE
        case else
            ! nothing
    end select
next
call close_file

end program
!
sub open_file

option type = explicit

%include "b.inc"

map (cmap) customer cbuf

open "b.isq" as file #1, indexed fixed, recordtype none, map cmap, primary key cbuf::id, alternate key cbuf::xname

end sub
!
function integer find_by_id(integer id, customer c)

option type = explicit

%include "b.inc"

map (cmap) customer cbuf

handler notf_handler
    find_by_id = FALSE
end handler
when error use notf_handler
    get #1, key #0 eq id
    c = cbuf
    find_by_id = TRUE
end when

end function
!
function integer find_by_name(string xname, customer c)

option type = explicit

%include "b.inc"

map (cmap) customer cbuf

handler notf_handler
    find_by_name = FALSE
end handler
when error use notf_handler
    get #1, key #1 eq xname
    c = cbuf
    find_by_name = TRUE
end when

end function
!
sub list_start
    reset #1
end sub
!
function integer list_next(customer c)

option type = explicit

%include "b.inc"

map (cmap) customer cbuf

handler eof_handler
   list_next = FALSE
end handler
when error use eof_handler
    get #1
    c = cbuf
    list_next = TRUE
end when

end function
!
function integer add(customer c)

option type = explicit

%include "b.inc"

map (cmap) customer cbuf

declare customer c2

external integer function find_by_id(integer, customer)
external integer function find_by_name(string, customer)

if (c::id > 0) and (c::xname <> "") and not find_by_id(c::id, c2) and not find_by_name(c::xname, c2) then
   cbuf = c
   put #1
   add = TRUE
else
   add = FALSE
end if

end function
!
sub close_file

option type = explicit

close #1

end sub
!
sub dump(customer c)

option type = explicit

%include "b.inc"

print using "id=#", c::id
print using "name='E", c::xname
print using "address='E", c::address

end sub

Create library:

b.inc:

declare integer constant TRUE = -1
declare integer constant FALSE = 0

record customer
    integer id
    string xname = 32
    string address = 64
end record

blib.bas:

sub open_file

option type = explicit

%include "b.inc"

map (cmap) customer cbuf

open "b.isq" as file #1, indexed fixed, recordtype none, map cmap, primary key cbuf::id, alternate key cbuf::xname

end sub
!
function integer find_by_id(integer id, customer c)

option type = explicit

%include "b.inc"

map (cmap) customer cbuf

handler notf_handler
    find_by_id = FALSE
end handler
when error use notf_handler
    get #1, key #0 eq id
    c = cbuf
    find_by_id = TRUE
end when

end function
!
function integer find_by_name(string xname, customer c)

option type = explicit

%include "b.inc"

map (cmap) customer cbuf

handler notf_handler
    find_by_name = FALSE
end handler
when error use notf_handler
    get #1, key #1 eq xname
    c = cbuf
    find_by_name = TRUE
end when

end function
!
sub list_start
    reset #1
end sub
!
function integer list_next(customer c)

option type = explicit

%include "b.inc"

map (cmap) customer cbuf

handler eof_handler
   list_next = FALSE
end handler
when error use eof_handler
    get #1
    c = cbuf
    list_next = TRUE
end when

end function
!
function integer add(customer c)

option type = explicit

%include "b.inc"

map (cmap) customer cbuf

declare customer c2

external integer function find_by_id(integer, customer)
external integer function find_by_name(string, customer)

if (c::id > 0) and (c::xname <> "") and not find_by_id(c::id, c2) and not find_by_name(c::xname, c2) then
   cbuf = c
   put #1
   add = TRUE
else
   add = FALSE
end if

end function
!
sub close_file

option type = explicit

close #1

end sub

bmain.bas:

program bmain

option type = explicit

%include "b.inc"

declare integer done, id
declare string sel, xname
declare customer c

external sub open_file
external integer function find_by_id(integer, customer)
external integer function find_by_name(string, customer)
external sub list_start
external integer function list_next(customer)
external integer function add(customer)
external sub close_file
external sub dump(customer)

call open_file
set no prompt
done = FALSE
while not done
    print "Options:"
    print "  1) Lookup by id"
    print "  2) Lookup by name"
    print "  3) List all"
    print "  4) Add"
    print "  5) Exit"
    input "Choice: "; sel
    select sel
        case = "1"
            input "Id: "; id
            if find_by_id(id, c) then
                print using "find by id: #", id
                call dump(c)
            else
                print using "not found: #", id
            end if
        case = "2"
            input "Name: "; xname 
            if find_by_name(xname, c) then
                print using "find by name: 'E", xname
                call dump(c)
            else
                print using "not found: 'E", xname
            end if
        case = "3"
            print "list"
            call list_start
            while list_next(c)
                call dump(c)
            next
        case = "4"
            input "Enter id: "; c::id
            input "Enter name: "; c::xname
            input "Enter address: "; c::address
            if add(c) then
                print "add succeeded"
            else
                print "add failed"
            end if
        case = "5"
            done = TRUE
        case else
            ! nothing
    end select
next
call close_file

end program
!
sub dump(customer c)

option type = explicit

%include "b.inc"

print using "id=#", c::id
print using "name='E", c::xname
print using "address='E", c::address

end sub

Build:

$ bas blib
$ bas bmain
$ link bmain + blib

Test library:

btest.bas:

program btest

option type = explicit

%include "b.inc"

external sub open_file
external sub test_find_by_id(integer)
external sub test_find_by_name(string)
external sub test_list
external sub test_add(integer, string, string)
external sub close_file

call open_file
call test_find_by_id(2)
call test_find_by_id(5)
call test_find_by_name("B B")
call test_find_by_name("E E")
call test_list
call test_add(5, "E E", "E road 5")
call test_list
call test_add(0, "X X", "X road")
call test_list
call close_file

end program
!
sub test_find_by_id(integer id)

option type = explicit

%include "b.inc"

declare customer c

external integer function find_by_id(integer, customer)
external sub dump(customer)

if find_by_id(id, c) then
    print using "find by id: #", id
    call dump(c)
else
    print using "not found: #", id
end if

end sub
!
sub test_find_by_name(string xname)

option type = explicit

%include "b.inc"

declare customer c

external integer function find_by_name(string, customer)
external sub dump(customer)

if find_by_name(xname, c) then
    print using "find by name: 'E", xname
    call dump(c)
else
    print using "not found: 'E", xname
end if

end sub
!
sub test_list

option type = explicit

%include "b.inc"

declare customer c

external sub list_start
external integer function list_next(customer)
external sub dump(customer)

print "list"
call list_start
while list_next(c)
    call dump(c)
next

end sub
!
sub test_add(integer id, string xname, string address)

option type = explicit

%include "b.inc"

declare customer c

external integer function add(customer)

c::id = id
c::xname = xname
c::address = address
if add(c) then
    print "add succeeded"
else
    print "add failed"
end if

end sub
!
sub dump(customer c)

option type = explicit

%include "b.inc"

print using "id=#", c::id
print using "name='E", c::xname
print using "address='E", c::address

end sub

Build and run:

$ bas btest
$ link btest + blib
$ run btest

Create shareable image:

bwrap.bas:

function integer b_open_file

option type = explicit

%include "$ssdef" %from %library "sys$library:basic$starlet.tlb"
%include "b.inc"

external sub open_file

call open_file
b_open_file = SS$_NORMAL

end function
!
function integer b_find_by_id(integer id, customer c)

option type = explicit

%include "$ssdef" %from %library "sys$library:basic$starlet.tlb"
%include "b.inc"

external integer function find_by_id(integer, customer)

if find_by_id(id, c) then
    b_find_by_id = SS$_NORMAL
else
    b_find_by_id = SS$_ABORT
end if

end function
!
function integer b_find_by_name(string xname, customer c)

option type = explicit

%include "$ssdef" %from %library "sys$library:basic$starlet.tlb"
%include "b.inc"

external integer function find_by_name(string, customer)

if find_by_name(xname, c) then
    b_find_by_name = SS$_NORMAL
else
    b_find_by_name = SS$_ABORT
end if

end function
!
function integer b_list_start

option type = explicit

%include "$ssdef" %from %library "sys$library:basic$starlet.tlb"

external sub list_start

call list_start
b_list_start = SS$_NORMAL

end function
!
function integer b_list_next(customer c)

option type = explicit

%include "$ssdef" %from %library "sys$library:basic$starlet.tlb"
%include "b.inc"

external integer function list_next(customer)

if list_next(c) then
    b_list_next = SS$_NORMAL
else
    b_list_next = SS$_ABORT
end if

end function
!
function integer b_add(customer c)

option type = explicit

%include "$ssdef" %from %library "sys$library:basic$starlet.tlb"
%include "b.inc"

external integer function add(customer)

if add(c) then
    b_add = SS$_NORMAL
else
    b_add = SS$_ABORT
end if

end function
!
function integer b_close_file

option type = explicit

%include "$ssdef" %from %library "sys$library:basic$starlet.tlb"

external sub close_file

call close_file
b_close_file = SS$_NORMAL

end function

Build:

$ bas bwrap
$ link/share=bshr bwrap + blib + sys$input/opt
symbol_vector=(b_open_file=procedure,-
               b_find_by_id=procedure,-
               b_find_by_name=procedure,-
               b_list_start=procedure,-
               b_list_next=procedure,-
               b_add=procedure,-
               b_close_file=procedure)
$
$ define/nolog bshr "''f$parse("bshr.exe")'"

Create TIG definition and artifacts:

b.xml:

<config>
    <image>bshr</image>
    <port>30002</port>
    <mt>false</mt>
    <server>
        <language>dk.vajhoej.vms.tig.server.JavaServerGen</language>
    </server>
    <client>
        <language>dk.vajhoej.vms.tig.client.JavaClientGen</language>
        <language>dk.vajhoej.vms.tig.client.CSharpClientGen</language>
        <language>dk.vajhoej.vms.tig.client.PyClientGen</language>
        <language>dk.vajhoej.vms.tig.client.PhpClientGen</language>
    </client>
    <methods>
        <method>
            <name>b_open_file</name>
            <args/>
        </method>
        <method>
            <name>b_find_by_id</name>
            <args>
                <arg>
                    <name>id</name>
                    <type>LongWord</type>
                    <pass>Reference</pass>
                    <use>In</use>
                </arg>
                <arg>
                    <name>c</name>
                    <type>Block</type>
                    <impl>BCustomer</impl>
                    <pass>Reference</pass>
                    <use>InOut</use>
                </arg>
            </args>
        </method>
        <method>
            <name>b_find_by_name</name>
            <args>
                <arg>
                    <name>name</name>
                    <type>FixedCharacterString</type>
                    <pass>Descriptor</pass>
                    <use>In</use>
                </arg>
                <arg>
                    <name>c</name>
                    <type>Block</type>
                    <impl>BCustomer</impl>
                    <pass>Reference</pass>
                    <use>InOut</use>
                </arg>
            </args>
        </method>
        <method>
            <name>b_list_start</name>
            <args/>
        </method>
        <method>
            <name>b_list_next</name>
            <args>
                <arg>
                    <name>c</name>
                    <type>Block</type>
                    <impl>BCustomer</impl>
                    <pass>Reference</pass>
                    <use>InOut</use>
                </arg>
            </args>
        </method>
        <method>
            <name>b_add</name>
            <args>
                <arg>
                    <name>c</name>                                                                                  
                    <type>Block</type>
                    <impl>BCustomer</impl>
                    <pass>Reference</pass>
                    <use>In</use>
                </arg>
            </args>
        </method>
        <method>
            <name>b_close_file</name>
            <args/>
        </method>
    </methods>
</config>

Gen:

java -cp vmstig.jar dk.vajhoej.vms.tig.Gen b.xml server client

Run server:

$ javac -cp vmstig.jar:vmscall.jar:record.jar BshrServer.java
$ java -cp .:vmstig.jar:vmscall.jar:record.jar "BshrServer"

Test client:

BCustomer.java:

import dk.vajhoej.record.FieldType;
import dk.vajhoej.record.Struct;
import dk.vajhoej.record.StructField;

@Struct
public class BCustomer {
    @StructField(n=0,type=FieldType.INT4)
    private int id;
    @StructField(n=1,type=FieldType.FIXSTR,length=32,pad=true,padchar=' ')
    private String name;
    @StructField(n=2,type=FieldType.FIXSTR,length=64,pad=true,padchar=' ')
    private String address;
    public BCustomer() {
        this(0, "", "");
    }
    public BCustomer(int id, String name, String address) {
        this.id = id;
        this.name = name;
        this.address = address;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
}

BTest.java:

import java.io.IOException; 

import dk.vajhoej.record.RecordException;

public class BTest {
    private static void dump(BCustomer c) {
        System.out.println("id=" + c.getId());
        System.out.println("name=" + c.getName());
        System.out.println("address=" + c.getAddress());
    }
    private static void testFindById(BshrClient cli, int id) throws IOException, RecordException {
        BCustomer[] c = new BCustomer[1];
        c[0] = new BCustomer();
        if(cli.b_find_by_id(id, c) % 2 == 1) {
            System.out.println("find by id: " + id);
            dump(c[0]);
        } else {
            System.out.println("not found: " + id);
        }
    }
    private static void testFindByName(BshrClient cli, String name) throws IOException, RecordException {
        BCustomer[] c = new BCustomer[1];
        c[0] = new BCustomer();
        if(cli.b_find_by_name(name, c) % 2 == 1) {
            System.out.println("find by name: " + name);
            dump(c[0]);
        } else {
            System.out.println("not found: " + name);
        }
    }
    private static void testList(BshrClient cli) throws IOException, RecordException {
        System.out.println("list");
        cli.b_list_start();
        BCustomer[] c = new BCustomer[1];
        c[0] = new BCustomer();
        while(cli.b_list_next(c) % 2 == 1) {
            dump(c[0]);
        }
    }
    private static void testAdd(BshrClient cli, int id, String name, String address) throws IOException, RecordException {
        BCustomer c = new BCustomer(id, name, address);
        if(cli.b_add(c) %2 == 1) {
            System.out.println("add succeeded");
        } else {
            System.out.println("add failed");
        }
    }
    public static void main(String[] args) throws IOException, RecordException {
        BshrClient cli = new BshrClient("arne4");
        cli.b_open_file();
        testFindById(cli, 2);
        testFindById(cli, 6);
        testFindByName(cli, "B B");
        testFindByName(cli, "F F");
        testList(cli);
        testAdd(cli, 6, "F F", "F road 6");
        testList(cli);
        testAdd(cli, 0, "X X", "X road");
        testList(cli);
        cli.b_close_file();
    }
}

Build and run:

javac -cp record.jar;vmstig.jar BCustomer.java BshrClient.java BTest.java
java -cp .;record.jar;vmstig.jar BTest

BCustomer.java:

import dk.vajhoej.record.FieldType;
import dk.vajhoej.record.Struct;
import dk.vajhoej.record.StructField;

@Struct
public class BCustomer {
    @StructField(n=0,type=FieldType.INT4)
    private int id;
    @StructField(n=1,type=FieldType.FIXSTR,length=32,pad=true,padchar=' ')
    private String name;
    @StructField(n=2,type=FieldType.FIXSTR,length=64,pad=true,padchar=' ')
    private String address;
    public BCustomer() {
        this(0, "", "");
    }
    public BCustomer(int id, String name, String address) {
        this.id = id;
        this.name = name;
        this.address = address;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
}

BTest.groovy:

def dump(c) {
    println("id=" + c.id)
    println("name=" + c.name)
    println("address=" + c.address)
}

def testFindById(cli, id) {
    c = new BCustomer[1]
    c[0] = new BCustomer()
    if(cli.b_find_by_id(id, c) % 2 == 1) {
        println("find by id: " + id)
        dump(c[0])
    } else {
        println("not found: " + id)
    }
}

def testFindByName(cli, name) {
    c = new BCustomer[1]
    c[0] = new BCustomer()
    if(cli.b_find_by_name(name, c) % 2 == 1) {
        println("find by name: " + name)
        dump(c[0])
    } else {
        println("not found: " + name)
    }
}

def testList(cli) {
    println("list")
    cli.b_list_start()
    c = new BCustomer[1]
    c[0] = new BCustomer()
    while(cli.b_list_next(c) % 2 == 1) {
        dump(c[0])
    }
}
def testAdd(cli, id, name, address) {
    c = new BCustomer(id, name, address)
    if(cli.b_add(c) %2 == 1) {
        println("add succeeded")
    } else {
        println("add failed")
    }
}

cli = new BshrClient("arne4")
cli.b_open_file()
testFindById(cli, 2)
testFindById(cli, 7)
testFindByName(cli, "B B")
testFindByName(cli, "G G")
testList(cli)
testAdd(cli, 7, "G G", "G road 7")
testList(cli)
testAdd(cli, 0, "X X", "X road")
testList(cli)
cli.b_close_file()

Test and run:

javac -cp record.jar;vmstig.jar BCustomer.java BshrClient.java
set classpath=.;record.jar;vmstig.jar
groovy BTest.groovy

BCustomer.cs:

using Vajhoej.Record;

[Struct]
public class BCustomer
{
	[StructField(N=0, Type=FieldType.INT4)]
	private int id;
	[StructField(N=1, Type=FieldType.FIXSTR, Length=32, Pad=true, PadChar=' ')]
	private string name;
	[StructField(N=2, Type=FieldType.FIXSTR, Length=64, Pad=true, PadChar=' ')]
	private string address;
	public BCustomer() : this(0, "", "")
	{
	}
	public BCustomer(int id, string name, string address)
	{
	    this.id = id;
	    this.name = name;
	    this.address = address;
	}
	public int Id
	{
		get { return id; }
		set { id = value; }
	}
	public string Name
	{
		get { return name; }
		set { name = value; }
	}
	public string Address
	{
		get { return address; }
		set { address = value; }
	}
}

BTest.cs:

using System;

public class BTest
{
    private static void Dump(BCustomer c)
    {
        Console.WriteLine("id=" + c.Id);
        Console.WriteLine("name=" + c.Name);
        Console.WriteLine("address=" + c.Address);
    }
    private static void TestFindById(BshrClient cli, int id)
    {
        BCustomer c = new BCustomer();
        if(cli.B_find_by_id(id, ref c) % 2 == 1)
        {
            Console.WriteLine("find by id: " + id);
            Dump(c);
        }
        else
        {
            Console.WriteLine("not found: " + id);
        }
    }
    private static void TestFindByName(BshrClient cli, string name)
    {
        BCustomer c = new BCustomer();
        if(cli.B_find_by_name(name, ref c) % 2 == 1)
        {
            Console.WriteLine("find by name: " + name);
            Dump(c);
        }
        else
        {
            Console.WriteLine("not found: " + name);
        }
    }
    private static void TestList(BshrClient cli)
    {
        Console.WriteLine("list");
        cli.B_list_start();
        BCustomer c = new BCustomer();
        while(cli.B_list_next(ref c) % 2 == 1)
        {
            Dump(c);
        }
    }
    private static void TestAdd(BshrClient cli, int id, String name, String address)
    {
        BCustomer c = new BCustomer(id, name, address);
        if(cli.B_add(c) %2 == 1)
        {
            Console.WriteLine("add succeeded");
        }
        else
        {
            Console.WriteLine("add failed");
        }
    }
    public static void Main(string[] args)
    {
        BshrClient cli = new BshrClient("ARNE4");
        cli.B_open_file();
        TestFindById(cli, 2);
        TestFindById(cli, 8);
        TestFindByName(cli, "B B");
        TestFindByName(cli, "H H");
        TestList(cli);
        TestAdd(cli, 8, "H H", "H road 8");
        TestList(cli);
        TestAdd(cli, 0, "X X", "X road");
        TestList(cli);
        cli.B_close_file();
    }
}

Test and run:

csc /r:tig.dll /r:record.dll BTest.cs BshrClient.cs BCustomer.cs
BTest

BCustomer.py:

import struct

class BCustomer:
    def __init__(self, id = 0, name = '', address = ''):
        self.id = id
        self.name = name
        self.address = address
    def pack(self):
        return struct.pack('<l32s64s', self.id, bytes(self.name.ljust(32,' '), 'iso-8859-1'), bytes(self.address.ljust(64,' '), 'iso-8859-1'))
    def unpack(self, blk):
        data = struct.unpack('<l32s64s', blk)
        self.id = data[0]
        self.name = str(data[1], 'iso-8859-1').rstrip(' \0')
        self.address = str(data[2], 'iso-8859-1').rstrip(' \0')

btest.py:

from BshrClient import BshrClient

from BCustomer import BCustomer

def dump(c):
    print('id=' + str(c.id))
    print('name=' + c.name)
    print('address=' + c.address)

def test_find_by_id(cli, id):
    c = BCustomer()
    stat, c = cli.b_find_by_id(id, c)
    if stat % 2 == 1:
        print('find by id: ' + str(id))
        dump(c)
    else:
        print('not found: ' + str(id))

def test_find_by_name(cli, name):
    c = BCustomer()
    stat, c = cli.b_find_by_name(name, c)
    if stat % 2 == 1:
        print('find by name: ' + name)
        dump(c)
    else:
        print('not found: ' + name)

def test_list(cli):
    print('list')
    cli.b_list_start()
    while True:
        c = BCustomer()
        stat, c = cli.b_list_next(c)
        if stat % 2 != 1: break
        dump(c)

def test_add(cli, id, name, address):
    c = BCustomer(id, name, address)
    if cli.b_add(c) % 2 == 1:
        print('add succeded')
    else:                                                                                                                  
        print('add failed')

cli = BshrClient('arne4')
cli.b_open_file()
test_find_by_id(cli, 2)
test_find_by_id(cli, 9)
test_find_by_name(cli, 'B B')
test_find_by_name(cli, 'I I')
test_list(cli)
test_add(cli, 9, 'I I', 'I road 9')
test_list(cli)
test_add(cli, 0, 'X X', 'X road')
test_list(cli)
cli.b_close_file()

Run:

python btest.py

BCustomer.php:

<?php

class BCustomer {
    public $id;
    public $name;
    public $address;
    function __construct($id = 0, $name = '', $address = '') {
        $this->id = $id;
        $this->name = $name;
        $this->address = $address;
    }
    function pack() {
        return pack('Va32a64', $this->id, str_pad($this->name, 32, ' '), str_pad($this->address, 64, ' '));
    }
    function unpack($blk) {
        $this->id = unpack('V', $blk, 0)[1];
        $this->name = trim(unpack('a32', $blk, 4)[1]);
        $this->address = trim(unpack('a64', $blk, 36)[1]);
    }
}

?>

btest.php:

<?php

include 'BCustomer.php';
include 'BshrClient.php';

function dump($c) {
    echo 'id=' . $c->id . "\r\n";
    echo 'name=' . $c->name . "\r\n";
    echo 'address=' . $c->address . "\r\n";
}

function test_find_by_id($cli, $id) {
    $c = new BCustomer();
    if($cli->b_find_by_id($id, $c) % 2 == 1) {
        echo 'find by id: ' . $id . "\r\n";
        dump($c);
    } else {
        echo 'not found: ' . $id . "\r\n";
    }
}

function test_find_by_name($cli, $name) {
    $c = new BCustomer();
    if($cli->b_find_by_name($name, $c) % 2 == 1) {
        echo 'find by name: ' . $name . "\r\n";
        dump($c);
    } else {
        echo 'not found: ' . $name . "\r\n";
    }
}

function test_list($cli) {
    echo "list\r\n";
    $cli->b_list_start();
    $c = new BCustomer();
    while($cli->b_list_next($c) % 2 == 1) {
        dump($c);
    }
}

function test_add($cli, $id, $name, $address) {
    $c = new BCustomer($id, $name, $address);
    if($cli->b_add($c) % 2 == 1) {
        echo "add succeeded\r\n";
    } else {
        echo "add failed\r\n";
    }
}

$cli = new BshrClient('arne4');
$cli->b_open_file();
test_find_by_id($cli, 2);
test_find_by_id($cli, 10);
test_find_by_name($cli, 'B B');
test_find_by_name($cli, 'J J');
test_list($cli);
test_add($cli, 10, 'J J', 'J road 10');
test_list($cli);
test_add($cli, 0, 'X X', 'X road');
test_list($cli);
$cli->b_close_file()

?>

Run:

php btest.php

Web application:

b.jsp:

<html>
<head>
<title>Bshr test</title>
</head>
<body>
<h1>Bshr test</h1>
<h2>Find by id:</h2>
<form method="post" action="b_find_by_id.jsp">
Id: <input type="text" name="id">
<br>
<input type="submit" value="Find">
</form>
<h2>Find by name:</h2>
<form method="post" action="b_find_by_name.jsp">
Name: <input type="text" name="name">
<br>
<input type="submit" value="Find">
</form>
<h2>List all:</h2>
<form method="post" action="b_list.jsp">
<input type="submit" value="List">
</form>                                                                                                                         
<h2>Add one:</h2>
<form method="post" action="b_add.jsp">
Id: <input type="text" name="id">
<br>
Name: <input type="text" name="name">
<br>
Address: <input type="text" name="address">
<br>
<input type="submit" value="Add">
</form>
</body>
</html>

b_find_by_id.jsp:

<%@ page import="bshr.*" %>
<html>
<head>
<title>Find by id</title>
</head>
<body>
<h1>Find by id</h1>
<h2>Result:</h2>
<%
int id = Integer.parseInt(request.getParameter("id"));
synchronized(BshrClient.class) {
    BshrClient cli = new BshrClient("arne4");
    cli.b_open_file();
    BCustomer[] c = new BCustomer[1];
    c[0] = new BCustomer();
    if(cli.b_find_by_id(id, c) % 2 == 1) {
        out.println("id = " + c[0].getId() + "<br>");
        out.println("name = " + c[0].getName() + "<br>");
        out.println("address = " + c[0].getAddress());
    } else {
        out.println("Error: id " + id + " not found");
    }
    cli.b_close_file();
}
%>
</body>
</html>

b_find_by_name.jsp:

<%@ page import="bshr.*" %>
<html>
<head>
<title>Find by name</title>
</head>
<body>
<h1>Find by name</h1>
<h2>Result:</h2>
<%
String name = request.getParameter("name");
synchronized(BshrClient.class) {
    BshrClient cli = new BshrClient("arne4");
    cli.b_open_file();
    BCustomer[] c = new BCustomer[1];
    c[0] = new BCustomer();
    if(cli.b_find_by_name(name, c) % 2 == 1) {
        out.println("id = " + c[0].getId() + "<br>");
        out.println("name = " + c[0].getName() + "<br>");
        out.println("address = " + c[0].getAddress());
    } else {
        out.println("Error: name " + name + " not found");
    }
    cli.b_close_file();
}
%>
</body>
</html>

b_list.jsp:

<%@ page import="bshr.*" %>
<html>
<head>
<title>List all</title>
</head>
<body>
<h1>List all</h1>
<h2>List:</h2>
<table border="1">
<%
synchronized(BshrClient.class) {
    BshrClient cli = new BshrClient("arne4");
    cli.b_open_file();
    cli.b_list_start();
    BCustomer[] c = new BCustomer[1];
    c[0] = new BCustomer();
    while(cli.b_list_next(c) % 2 == 1) {
        out.println("<tr>");
        out.println("<td>" + c[0].getId() + "</td>");
        out.println("<td>" + c[0].getName() + "</td>");
        out.println("<td>" + c[0].getAddress() + "</td>");
        out.println("</tr>");
    }
    cli.b_close_file();
}
%>
</table>
</body>
</html>

b_add.jsp:

<%@ page import="bshr.*" %>
<html>
<head>
<title>Add one</title>
</head>
<body>
<h1>Add one</h1>
<h2>Status:</h2>
<%
int id = Integer.parseInt(request.getParameter("id"));
String name = request.getParameter("name");
String address = request.getParameter("address");
synchronized(BshrClient.class) {
    BshrClient cli = new BshrClient("arne4");
    cli.b_open_file();
    BCustomer c = new BCustomer(id, name, address);
    if(cli.b_add(c) % 2 == 1) {
        out.println(id + " added");
    } else {
        out.println(id + " not added");
    }
    cli.b_close_file();
}
%>
</body>
</html>

b.php:

<html>
<head>
<title>Bshr test</title>
</head>
<body>
<h1>Bshr test</h1>
<h2>Find by id:</h2>
<form method="post" action="b_find_by_id.php">
Id: <input type="text" name="id">
<br>
<input type="submit" value="Find">
</form>
<h2>Find by name:</h2>
<form method="post" action="b_find_by_name.php">
Name: <input type="text" name="name">
<br>
<input type="submit" value="Find">
</form>
<h2>List all:</h2>
<form method="post" action="b_list.php">
<input type="submit" value="List">
</form>
<h2>Add one:</h2>
<form method="post" action="b_add.php">
Id: <input type="text" name="id">
<br>
Name: <input type="text" name="name">
<br>
Address: <input type="text" name="address">
<br>
<input type="submit" value="Add">
</form>
</body>
</html>

b_find_by_id.php:

<html>
<head>
<title>Find by id</title>
</head>
<body>
<h1>Find by id</h1>
<h2>Result:</h2>
<?php

include 'BCustomer.php';
include 'BshrClient.php';

$id = (int)$_POST['id'];
$sem = sem_get(30002);
sem_acquire($sem);
$cli = new BshrClient('arne4');
$cli->b_open_file();
$c = new BCustomer();
if($cli->b_find_by_id($id, $c) % 2 == 1) {
    echo 'id = ' . $c->id . "<br>";
    echo 'name = ' . $c->name . "<br>";
    echo 'address = ' . $c->address . "";
} else {
    echo "Error: id $id not found";
}
$cli->b_close_file();
sem_release($sem);

?>
</body>
</html>

b_find_by_name.php:

<html>
<head>
<title>Find by name</title>
</head>
<body>
<h1>Find by name</h1>
<h2>Result:</h2>
<?php

include 'BCustomer.php';
include 'BshrClient.php';

$name = $_POST['name'];
$cli = new BshrClient('arne4');
$sem = sem_get(30002);
sem_acquire($sem);
$cli->b_open_file();
$c = new BCustomer();
if($cli->b_find_by_name($name, $c) % 2 == 1) {
    echo 'id = ' . $c->id . "<br>";
    echo 'name = ' . $c->name . "<br>";
    echo 'address = ' . $c->address . "";
} else {
    echo "Error: name $name not found";
}
$cli->b_close_file();
sem_release($sem);

?>
</body>
</html>

b_list.php:

<html>
<head>
<title>List all</title>
</head>
<body>
<h1>List all</h1>
<h2>List:</h2>
<table border="1">
<?php

include 'BCustomer.php';
include 'BshrClient.php';

$sem = sem_get(30001);
sem_acquire($sem);
$cli = new BshrClient('arne4');
$cli->b_open_file();
$cli->b_list_start();
$c = new BCustomer();
while($cli->b_list_next($c) % 2 == 1) {
    echo '<tr>';
    echo '<td>' . $c->id . '</td>';
    echo '<td>' . $c->name . '</td>';
    echo '<td>' . $c->address . '</td>';
    echo '</tr>';
}
$cli->b_close_file();
sem_release($sem);

?>
</table>
</body>
</html>

b_add.php:

<html>
<head>
<title>Add one</title>
</head>
<body>
<h1>Add one</h1>
<h2>Status:</h2>
<?php

include 'BCustomer.php';
include 'BshrClient.php';

$id = (int)$_POST['id'];
$name = $_POST['name'];
$address = $_POST['address'];
$sem = sem_get(30001);
sem_acquire($sem);
$cli = new BshrClient('arne4');
$cli->b_open_file();
$c = new BCustomer($id, $name, $address);
if($cli->b_add($c) % 2 == 1) {                                                             
    echo "$id added";
} else {
    echo "$id not added";
}
$cli->b_close_file();
sem_release($sem);

?>
</body>
</html>

Cobol:

Setup:

Cobol

Starting point:

o.inc:

01 customer-record.
    03 customer-id pic 9(7) display.
    03 customer-name pic x(32).
    03 customer-address pic x(64).

oload.cob:

identification division.
program-id.oload.

environment division.
input-output section.
file-control.
    select optional customer-file assign to "o.isq"
                                  organization is indexed
                                  access mode is dynamic
                                  record key is customer-id
                                  alternate record key is customer-name.

data division.
file section.
fd customer-file.
copy "o.inc".
working-storage section.

procedure division.
main-paragraph.
    open i-o customer-file
    move 1 to customer-id
    move "A A" to customer-name
    move "A road 1" to customer-address
    perform insert-paragraph
    move 2 to customer-id
    move "B B" to customer-name
    move "B road 2" to customer-address
    perform insert-paragraph
    move 3 to customer-id
    move "C C" to customer-name
    move "C road 3" to customer-address
    perform insert-paragraph
    close customer-file
    stop run.
insert-paragraph.
    write customer-record
        invalid key display "Error writing customer"
        not invalid key continue
    end-write.

omain.cob:

identification division.
program-id.omain.

environment division.
input-output section.
file-control.
    select optional customer-file assign to "o.isq"
                                  organization is indexed
                                  access mode is dynamic
                                  record key is customer-id
                                  alternate record key is customer-name.

data division.
file section.
fd customer-file.
copy "o.inc".
working-storage section.
01 done pic x.
01 sel pic x.
01 eof pic x.
01 fnd pic x.
01 xid pic 9(7) display.
01 name pic x(32).
01 address pic x(64).

procedure division.
main-paragraph.
    perform open-file-paragraph
    move "N" to done
    perform until done = "Y"
        display "Options:"
        display "  1) Lookup by id"
        display "  2) Lookup by name"
        display "  3) List all"
        display "  4) Add"
        display "  5) Exit"
        display "Choice: " with no advancing
        accept sel
        evaluate sel
            when "1"
                display "Enter id: " with no advancing
                accept xid
                perform find-by-id-paragraph
                if fnd = "Y"
                    display "find by id: ", xid
                    perform dump-paragraph
                else
                    display "not found: ", xid
                end-if
            when "2"
                display "Enter name: " with no advancing
                accept name
                perform find-by-name-paragraph
                if fnd = "Y"
                    display "find by name: ", name
                    perform dump-paragraph
                else
                    display "not found: ", name
                end-if
            when "3"
                display "list"
                perform list-start-paragraph
                move "N" to eof
                perform until eof = "Y"
                    perform list-next-paragraph
                    if eof not = "Y"
                        perform dump-paragraph
                    end-if
                end-perform
            when "4"
                display "Enter id: " with no advancing
                accept xid
                display "Enter name: " with no advancing
                accept name
                display "Enter address: " with no advancing
                accept address
                perform add-paragraph
                if fnd = "Y"
                    display "add succeeded"
                else
                    display "add failed"
                end-if
            when "5"
                move "Y" to done
        end-evaluate
    end-perform
    perform close-file-paragraph
    stop run.
open-file-paragraph.
    open i-o customer-file.
find-by-id-paragraph.
    move xid to customer-id
    read customer-file
        invalid key move "N" to fnd
        not invalid key move "Y" to fnd
    end-read.
find-by-name-paragraph.
    move name to customer-name
    read customer-file key is customer-name
        invalid key move "N" to fnd
        not invalid key move "Y" to fnd
    end-read.
list-start-paragraph.
    move 0 to customer-id
    start customer-file key is greater than customer-id
        invalid key display "error rewinding"
        not invalid key continue
    end-start.
list-next-paragraph.
    read customer-file next
        at end move "Y" to eof
        not at end continue
    end-read.
add-paragraph.
    if xid > 0 and name not = ""
        perform find-by-id-paragraph
        if fnd = "N"
            perform find-by-name-paragraph
            if fnd = "N"
                move xid to customer-id
                move name to customer-name
                move address to customer-address
                write customer-record
                    invalid key display "error writing"
                    not invalid key continue
                end-write
                move "Y" to fnd
            else
                move "N" to fnd
            end-if
        else
            move "N" to fnd
        end-if
    else
        move "N" to fnd
    end-if.
close-file-paragraph.
    close customer-file.
dump-paragraph.
    display "id=" customer-id
    display "name=" customer-name
    display "address=" customer-address.

Create library:

o.inc:

01 customer-record.
    03 customer-id pic 9(7) display.
    03 customer-name pic x(32).
    03 customer-address pic x(64).

o2.inc:

01 c-record.
    03 c-id pic 9(7) display.
    03 c-name pic x(32).
    03 c-address pic x(64).

olib.cob:

identification division.
program-id.open-file.

environment division.
input-output section.
file-control.
    select optional customer-file assign to "o.isq"
                                  organization is indexed
                                  access mode is dynamic
                                  record key is customer-id
                                  alternate record key is customer-name.

data division.
file section.
fd customer-file external.
copy "o.inc".
working-storage section.

procedure division.
main-paragraph.
    open i-o customer-file.
end program open-file.
****
identification division.
program-id.find-by-id.

environment division.
input-output section.
file-control.
    select optional customer-file assign to "o.isq"
                                  organization is indexed
                                  access mode is dynamic
                                  record key is customer-id
                                  alternate record key is customer-name.

data division.
file section.
fd customer-file external.
copy "o.inc".
working-storage section.
linkage section.
01 xid pic 9(7) display.
copy "o2.inc".
01 fnd pic x.

procedure division using xid, c-record, fnd.
main-paragraph.
    move xid to customer-id
    read customer-file
        invalid key move "N" to fnd
        not invalid key move "Y" to fnd
    end-read
    move customer-record to c-record.
end program find-by-id.
****
identification division.
program-id.find-by-name.

environment division.
input-output section.
file-control.
    select optional customer-file assign to "o.isq"
                                  organization is indexed
                                  access mode is dynamic
                                  record key is customer-id
                                  alternate record key is customer-name.

data division.
file section.
fd customer-file external.
copy "o.inc".
working-storage section.
linkage section.
01 name pic x(32).
copy "o2.inc".
01 fnd pic x.

procedure division using name, c-record, fnd.
main-paragraph.
    move name to customer-name
    read customer-file key is customer-name
        invalid key move "N" to fnd
        not invalid key move "Y" to fnd
    end-read
    move customer-record to c-record.
end program find-by-name.
****
identification division.
program-id.list-start.

environment division.
input-output section.
file-control.
    select optional customer-file assign to "o.isq"
                                  organization is indexed
                                  access mode is dynamic
                                  record key is customer-id
                                  alternate record key is customer-name.

data division.
file section.
fd customer-file external.
copy "o.inc".
working-storage section.

procedure division.
main-paragraph.
    move 0 to customer-id
    start customer-file key is greater than customer-id
        invalid key display "error rewinding"
        not invalid key continue
    end-start.
end program list-start.
****
identification division.
program-id.list-next.

environment division.
input-output section.
file-control.
    select optional customer-file assign to "o.isq"
                                  organization is indexed
                                  access mode is dynamic
                                  record key is customer-id
                                  alternate record key is customer-name.

data division.
file section.
fd customer-file external.
copy "o.inc".
working-storage section.
linkage section.
copy "o2.inc".
01 eof pic x.

procedure division using c-record, eof.
main-paragraph.
    read customer-file next
        at end move "Y" to eof
        not at end
            move customer-record to c-record
            move "N" to eof
    end-read.
end program list-next.
****
identification division.
program-id.xadd.

environment division.
input-output section.
file-control.
    select optional customer-file assign to "o.isq"
                                  organization is indexed
                                  access mode is dynamic
                                  record key is customer-id
                                  alternate record key is customer-name.

data division.
file section.
fd customer-file external.
copy "o.inc".
working-storage section.
01 fnd pic x.
01 c2-record.
    03 c2-id pic 9(7) display.
    03 c2-name pic x(32).
    03 c2-address pic x(64).
linkage section.
copy "o2.inc".
01 added pic x.

procedure division using c-record, added.
main-paragraph.
    if c-id > 0 and c-name not = ""
        call "find-by-id" using c-id, c2-record, fnd end-call
        if fnd = "N"
            call "find-by-name" using c-name, c2-record, fnd end-call
            if fnd = "N"
                move c-record to customer-record
                write customer-record
                    invalid key display "error writing"
                    not invalid key continue
                end-write
                move "Y" to added
            else
                move "N" to added
            end-if
        else
            move "N" to added
        end-if
    else
        move "N" to added
    end-if.
end program xadd.
****
identification division.
program-id.close-file.

environment division.
input-output section.
file-control.
    select optional customer-file assign to "o.isq"
                                  organization is indexed
                                  access mode is dynamic
                                  record key is customer-id
                                  alternate record key is customer-name.

data division.
file section.
fd customer-file external.
copy "o.inc".
working-storage section.

procedure division.
main-paragraph.
    close customer-file.
end program close-file.

omain.cob:

identification division.
program-id.omain.

environment division.
input-output section.

data division.
file section.
working-storage section.
copy "o2.inc".
01 done pic x.
01 sel pic x.
01 eof pic x.
01 fnd pic x.
01 xid pic 9(7) display.
01 name pic x(32).
01 temp pic 9(7) display.

procedure division.
main-paragraph.
    call "open-file"
    move "N" to done
    perform until done = "Y"
        display "Options:"
        display "  1) Lookup by id"
        display "  2) Lookup by name"
        display "  3) List all"
        display "  4) Add"
        display "  5) Exit"
        display "Choice: " with no advancing
        accept sel
        evaluate sel
            when "1"
                display "Enter id: " with no advancing
                accept xid
                call "find-by-id" using xid, c-record, fnd end-call
                if fnd = "Y"
                    display "find by id: ", xid
                    perform dump-paragraph
                else
                    display "not found: ", xid
                end-if
            when "2"
                display "Enter name: " with no advancing
                accept name
                call "find-by-name" using name, c-record, fnd end-call
                if fnd = "Y"
                    display "find by name: ", name
                    perform dump-paragraph
                else
                    display "not found: ", name
                end-if
            when "3"
                display "list"
                call "list-start"
                move "N" to eof
                perform until eof = "Y"
                    call "list-next" using c-record, eof end-call
                    if eof not = "Y"
                        perform dump-paragraph
                    end-if
                end-perform
            when "4"
                display "Enter id: " with no advancing
                accept temp
                move temp to c-id
                display "Enter name: " with no advancing
                accept c-name
                display "Enter address: " with no advancing
                accept c-address
                call "xadd" using c-record, fnd end-call
                if fnd = "Y"
                    display "add succeeded"
                else
                    display "add failed"
                end-if
            when "5"
                move "Y" to done
        end-evaluate
    end-perform
    call "close-file"
    stop run.
dump-paragraph.
    display "id=" c-id
    display "name=" c-name
    display "address=" c-address.

Build:

$ cob olib
$ cob omain
$ link omain + olib

Test library:

otest.cob:

identification division.
program-id.omain.

environment division.
input-output section.

data division.
file section.
working-storage section.
copy "o2.inc".
01 done pic x.
01 sel pic x.
01 eof pic x.
01 fnd pic x.
01 added pic x.
01 xid pic 9(7) display.
01 name pic x(32).

procedure division.
main-paragraph.
    call "open-file"
    move 2 to xid
    perform test-find-by-id-paragraph
    move 5 to xid
    perform test-find-by-id-paragraph
    move "B B" to name
    perform test-find-by-name-paragraph
    move "E E" to name
    perform test-find-by-name-paragraph
    perform test-list-paragraph
    move 5 to c-id
    move "E E" to c-name
    move "E road 5" to c-address
    perform test-add-paragraph
    move 0 to c-id
    move "X X" to c-name
    move "X road" to c-address
    perform test-add-paragraph
    perform test-list-paragraph
    call "close-file"
    stop run.
test-find-by-id-paragraph.
    call "find-by-id" using xid, c-record, fnd end-call
    if fnd = "Y"
        display "find by id: ", xid
        perform dump-paragraph
    else
        display "not found: ", xid
    end-if.
test-find-by-name-paragraph.
    call "find-by-name" using name, c-record, fnd end-call
    if fnd = "Y"
        display "find by name: ", name
        perform dump-paragraph
    else
        display "not found: ", name
    end-if.
test-list-paragraph.
    display "list"
    call "list-start"
    move "N" to eof
    perform until eof = "Y"
        call "list-next" using c-record, eof end-call
        if eof not = "Y"
            perform dump-paragraph
        end-if
    end-perform.
test-add-paragraph.
    call "xadd" using c-record, added end-call
    if added = "Y"
        display "add succeeded"
    else
        display "add failed"
    end-if.
dump-paragraph.
    display "id=" c-id
    display "name=" c-name
    display "address=" c-address.

Build and run:

$ cob otest
$ link otest + olib
$ run otest

Create shareable image:

owrap.cob:

identification division.
program-id.o-open-file.

environment division.
input-output section.

data division.
file section.
working-storage section.
01 stat pic 9(8) comp.
01 SS$_NORMAL pic 9(8) comp value is external SS$_NORMAL.
01 SS$_ABORT pic 9(8) comp value is external SS$_ABORT.

procedure division giving stat.
main-paragraph.
    call "open-file"
    move SS$_NORMAL to stat.
end program o-open-file.
****
identification division.
program-id.o-find-by-id.

environment division.
input-output section.

data division.
file section.
working-storage section.
01 fnd pic x.
01 stat pic 9(8) comp.
01 SS$_NORMAL pic 9(8) comp value is external SS$_NORMAL.
01 SS$_ABORT pic 9(8) comp value is external SS$_ABORT.
linkage section.
01 xid pic 9(7) display.
copy "o2.inc".

procedure division using xid, c-record giving stat.
main-paragraph.
    call "find-by-id" using xid, c-record, fnd end-call
    if fnd = "Y"
        move SS$_NORMAL to stat
    else
        move SS$_ABORT to stat
    end-if.
end program o-find-by-id.
****
identification division.
program-id.o-find-by-name.

environment division.
input-output section.

data division.
file section.
working-storage section.
01 fnd pic x.
01 stat pic 9(8) comp.
01 SS$_NORMAL pic 9(8) comp value is external SS$_NORMAL.
01 SS$_ABORT pic 9(8) comp value is external SS$_ABORT.
linkage section.
01 name pic x(32).
copy "o2.inc".

procedure division using name, c-record giving stat.
main-paragraph.
    call "find-by-name" using name, c-record, fnd end-call
    if fnd = "Y"
        move SS$_NORMAL to stat
    else
        move SS$_ABORT to stat
    end-if.
end program o-find-by-name.
****
identification division.
program-id.o-list-start.

environment division.
input-output section.

data division.
file section.
working-storage section.
01 stat pic 9(8) comp.
01 SS$_NORMAL pic 9(8) comp value is external SS$_NORMAL.
01 SS$_ABORT pic 9(8) comp value is external SS$_ABORT.

procedure division giving stat.
main-paragraph.
    call "list-start"
    move SS$_NORMAL to stat.
end program o-list-start.
****
identification division.
program-id.o-list-next.

environment division.
input-output section.

data division.
file section.
working-storage section.
01 eof pic x.
01 stat pic 9(8) comp.
01 SS$_NORMAL pic 9(8) comp value is external SS$_NORMAL.
01 SS$_ABORT pic 9(8) comp value is external SS$_ABORT.
linkage section.
copy "o2.inc".

procedure division using c-record giving stat.
main-paragraph.
    call "list-next" using c-record, eof end-call
    if eof not = "Y"
        move SS$_NORMAL to stat
    else
        move SS$_ABORT to stat
    end-if.
end program o-list-next.
****
identification division.
program-id.o-add.

environment division.
input-output section.

data division.
file section.
working-storage section.
01 added pic x.
01 stat pic 9(8) comp.
01 SS$_NORMAL pic 9(8) comp value is external SS$_NORMAL.
01 SS$_ABORT pic 9(8) comp value is external SS$_ABORT.
linkage section.
copy "o2.inc".

procedure division using c-record giving stat.
main-paragraph.
    call "xadd" using c-record, added end-call
    if added = "Y"
        move SS$_NORMAL to stat
    else
        move SS$_ABORT to stat
    end-if.
end program o-add.
****
identification division.
program-id.o-close-file.

environment division.
input-output section.

data division.
working-storage section.
01 stat pic 9(8) comp.
01 SS$_NORMAL pic 9(8) comp value is external SS$_NORMAL.
01 SS$_ABORT pic 9(8) comp value is external SS$_ABORT.

procedure division giving stat.
main-paragraph.
    call "close-file"
    move SS$_NORMAL to stat.
end program o-close-file.

Build:

$ cob owrap
$ link/share=oshr owrap + olib + sys$input/opt
symbol_vector=(o_open_file=procedure,-
               o_find_by_id=procedure,-
               o_find_by_name=procedure,-
               o_list_start=procedure,-
               o_list_next=procedure,-
               o_add=procedure,-
               o_close_file=procedure)
$
$ define/nolog oshr "''f$parse("oshr.exe")'"

Create TIG definition and artifacts:

o.xml:

<config>
    <image>oshr</image>
    <port>30003</port>
    <mt>false</mt>
    <server>
        <language>dk.vajhoej.vms.tig.server.JavaServerGen</language>
    </server>
    <client>
        <language>dk.vajhoej.vms.tig.client.JavaClientGen</language>
        <language>dk.vajhoej.vms.tig.client.CSharpClientGen</language>
        <language>dk.vajhoej.vms.tig.client.PyClientGen</language>
        <language>dk.vajhoej.vms.tig.client.PhpClientGen</language>
    </client>
    <methods>
        <method>
            <name>o_open_file</name>
            <args/>
        </method>
        <method>
            <name>o_find_by_id</name>
            <args>
                <arg>
                    <name>id</name>
                    <type>FixedCharacterString</type>
                    <pass>Reference</pass>
                    <use>In</use>
                </arg>
                <arg>
                    <name>c</name>
                    <type>Block</type>
                    <impl>OCustomer</impl>
                    <pass>Reference</pass>
                    <use>InOut</use>
                </arg>
            </args>
        </method>
        <method>
            <name>o_find_by_name</name>
            <args>
                <arg>
                    <name>name</name>
                    <type>FixedCharacterString</type>
                    <pass>Reference</pass>
                    <use>In</use>
                </arg>
                <arg>
                    <name>c</name>
                    <type>Block</type>
                    <impl>OCustomer</impl>
                    <pass>Reference</pass>
                    <use>InOut</use>
                </arg>
            </args>
        </method>
        <method>
            <name>o_list_start</name>
            <args/>
        </method>
        <method>
            <name>o_list_next</name>
            <args>
                <arg>
                    <name>c</name>
                    <type>Block</type>
                    <impl>OCustomer</impl>
                    <pass>Reference</pass>
                    <use>InOut</use>
                </arg>
            </args>
        </method>
        <method>
            <name>o_add</name>
            <args>
                <arg>
                    <name>c</name>
                    <type>Block</type>
                    <impl>OCustomer</impl>
                    <pass>Reference</pass>
                    <use>In</use>
                </arg>
            </args>
        </method>
        <method>
            <name>o_close_file</name>
            <args/>
        </method>
    </methods>
</config>

Gen:

java -cp vmstig.jar dk.vajhoej.vms.tig.Gen o.xml server client

Run server:

$ javac -cp vmstig.jar:vmscall.jar:record.jar OshrServer.java
$ java -cp .:vmstig.jar:vmscall.jar:record.jar "OshrServer"

Test client:

OCustomer.java:

import dk.vajhoej.record.FieldType;
import dk.vajhoej.record.Struct;
import dk.vajhoej.record.StructField;

@Struct
public class OCustomer {
    public static String format(int id) {
        return String.format("%07d", id);
    }
    public static int parse(String id) {
        return Integer.parseInt(id);
    }
    public static String pad(String name) {
        return name + "                                ".substring(0, 32 - name.length());
    }
    @StructField(n=0,type=FieldType.FIXSTR,length=7)
    private String id;
    @StructField(n=1,type=FieldType.FIXSTR,length=32,pad=true,padchar=' ')
    private String name;
    @StructField(n=2,type=FieldType.FIXSTR,length=64,pad=true,padchar=' ')
    private String address;
    public OCustomer() {
        this(0, "", "");
    }
    public OCustomer(int id, String name, String address) {
        this.id = format(id);
        this.name = name;
        this.address = address;
    }
    public int getId() {
        return parse(id);
    }
    public void setId(int id) {
        this.id = format(id);
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
}

OTest.java:

import java.io.IOException; 

import dk.vajhoej.record.RecordException;

public class OTest {
    private static void dump(OCustomer c) {
        System.out.println("id=" + c.getId());
        System.out.println("name=" + c.getName());
        System.out.println("address=" + c.getAddress());
    }
    private static void testFindById(OshrClient cli, int id) throws IOException, RecordException {
        OCustomer[] c = new OCustomer[1];
        c[0] = new OCustomer();
        if(cli.o_find_by_id(OCustomer.format(id), c) % 2 == 1) {
            System.out.println("find by id: " + id);
            dump(c[0]);
        } else {
            System.out.println("not found: " + id);
        }
    }
    private static void testFindByName(OshrClient cli, String name) throws IOException, RecordException {
        OCustomer[] c = new OCustomer[1];
        c[0] = new OCustomer();
        if(cli.o_find_by_name(OCustomer.pad(name), c) % 2 == 1) {
            System.out.println("find by name: " + name);
            dump(c[0]);
        } else {
            System.out.println("not found: " + name);
        }
    }
    private static void testList(OshrClient cli) throws IOException, RecordException {
        System.out.println("list");
        cli.o_list_start();
        OCustomer[] c = new OCustomer[1];
        c[0] = new OCustomer();
        while(cli.o_list_next(c) % 2 == 1) {
            dump(c[0]);
        }
    }
    private static void testAdd(OshrClient cli, int id, String name, String address) throws IOException, RecordException {
        OCustomer c = new OCustomer(id, name, address);
        if(cli.o_add(c) %2 == 1) {
            System.out.println("add succeeded");
        } else {
            System.out.println("add failed");
        }
    }
    public static void main(String[] args) throws IOException, RecordException {
        OshrClient cli = new OshrClient("arne4");
        cli.o_open_file();
        testFindById(cli, 2);
        testFindById(cli, 6);
        testFindByName(cli, "B B");
        testFindByName(cli, "F F");
        testList(cli);
        testAdd(cli, 6, "F F", "F road 6");
        testList(cli);
        testAdd(cli, 0, "X X", "X road");
        testList(cli);
        cli.o_close_file();
    }
}

Build and run:

javac -cp record.jar;vmstig.jar OCustomer.java OshrClient.java OTest.java
java -cp .;record.jar;vmstig.jar OTest

OCustomer.java:

import dk.vajhoej.record.FieldType;
import dk.vajhoej.record.Struct;
import dk.vajhoej.record.StructField;

@Struct
public class OCustomer {
    public static String format(int id) {
        return String.format("%07d", id);
    }
    public static int parse(String id) {
        return Integer.parseInt(id);
    }
    public static String pad(String name) {
        return name + "                                ".substring(0, 32 - name.length());
    }
    @StructField(n=0,type=FieldType.FIXSTR,length=7)
    private String id;
    @StructField(n=1,type=FieldType.FIXSTR,length=32,pad=true,padchar=' ')
    private String name;
    @StructField(n=2,type=FieldType.FIXSTR,length=64,pad=true,padchar=' ')
    private String address;
    public OCustomer() {
        this(0, "", "");
    }
    public OCustomer(int id, String name, String address) {
        this.id = format(id);
        this.name = name;
        this.address = address;
    }
    public int getId() {
        return parse(id);
    }
    public void setId(int id) {
        this.id = format(id);
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
}

OTest.groovy:

def dump(c) {
    println("id=" + c.id)
    println("name=" + c.name)
    println("address=" + c.address)
}

def testFindById(cli, id) {
    c = new OCustomer[1]
    c[0] = new OCustomer()
    if(cli.o_find_by_id(OCustomer.format(id), c) % 2 == 1) {
        println("find by id: " + id)
        dump(c[0])
    } else {
        println("not found: " + id)
    }
}

def testFindByName(cli, name) {
    c = new OCustomer[1]
    c[0] = new OCustomer()
    if(cli.o_find_by_name(OCustomer.pad(name), c) % 2 == 1) {
        println("find by name: " + name)
        dump(c[0])
    } else {
        println("not found: " + name)
    }
}

def testList(cli) {
    println("list")
    cli.o_list_start()
    c = new OCustomer[1]
    c[0] = new OCustomer()
    while(cli.o_list_next(c) % 2 == 1) {
        dump(c[0])
    }
}
def testAdd(cli, id, name, address) {
    c = new OCustomer(id, name, address)
    if(cli.o_add(c) %2 == 1) {
        println("add succeeded")
    } else {
        println("add failed")
    }
}

cli = new OshrClient("arne4")
cli.o_open_file()
testFindById(cli, 2)
testFindById(cli, 7)
testFindByName(cli, "B B")
testFindByName(cli, "G G")
testList(cli)
testAdd(cli, 7, "G G", "G road 7")
testList(cli)
testAdd(cli, 0, "X X", "X road")
testList(cli)
cli.o_close_file()

Build and run:

javac -cp record.jar;vmstig.jar OCustomer.java OshrClient.java
set classpath=.;record.jar;vmstig.jar
groovy OTest.groovy

OCustomer.cs:

using Vajhoej.Record;

[Struct]
public class OCustomer
{
    public static string Format(int id)
    {
        return id.ToString("0000000");
    }
    public static int Parse(string id)
    {
        return int.Parse(id);
    }
    public static string Pad(string name)
    {
        return name + "                                    ".Substring(0, 32 - name.Length);
    }
	[StructField(N=0, Type=FieldType.FIXSTR, Length=7)]
	private string id;
	[StructField(N=1, Type=FieldType.FIXSTR, Length=32, Pad=true, PadChar=' ')]
	private string name;
	[StructField(N=2, Type=FieldType.FIXSTR, Length=64, Pad=true, PadChar=' ')]
	private string address;
	public OCustomer() : this(0, "", "")
	{
	}
	public OCustomer(int id, string name, string address)
	{
	    this.id = Format(id);
	    this.name = name;
	    this.address = address;
	}
	public int Id
	{
		get { return Parse(id); }
		set { id = Format(value); }
	}
	public string Name
	{
		get { return name; }
		set { name = value; }
	}
	public string Address
	{
		get { return address; }
		set { address = value; }
	}
}

OTest.cs:

using System;

public class OTest
{
    private static void Dump(OCustomer c)
    {
        Console.WriteLine("id=" + c.Id);
        Console.WriteLine("name=" + c.Name);
        Console.WriteLine("address=" + c.Address);
    }
    private static void TestFindById(OshrClient cli, int id)
    {
        OCustomer c = new OCustomer();
        if(cli.O_find_by_id(OCustomer.Format(id), ref c) % 2 == 1)
        {
            Console.WriteLine("find by id: " + id);
            Dump(c);
        }
        else
        {
            Console.WriteLine("not found: " + id);
        }
    }
    private static void TestFindByName(OshrClient cli, string name)
    {
        OCustomer c = new OCustomer();
        if(cli.O_find_by_name(OCustomer.Pad(name), ref c) % 2 == 1)
        {
            Console.WriteLine("find by name: " + name);
            Dump(c);
        }
        else
        {
            Console.WriteLine("not found: " + name);
        }
    }
    private static void TestList(OshrClient cli)
    {
        Console.WriteLine("list");
        cli.O_list_start();
        OCustomer c = new OCustomer();
        while(cli.O_list_next(ref c) % 2 == 1)
        {
            Dump(c);
        }
    }
    private static void TestAdd(OshrClient cli, int id, String name, String address)
    {
        OCustomer c = new OCustomer(id, name, address);
        if(cli.O_add(c) %2 == 1)
        {
            Console.WriteLine("add succeeded");
        }
        else
        {
            Console.WriteLine("add failed");
        }
    }
    public static void Main(string[] args)
    {
        OshrClient cli = new OshrClient("ARNE4");
        cli.O_open_file();
        TestFindById(cli, 2);
        TestFindById(cli, 8);
        TestFindByName(cli, "B B");
        TestFindByName(cli, "H H");
        TestList(cli);
        TestAdd(cli, 8, "H H", "H road 8");
        TestList(cli);
        TestAdd(cli, 0, "X X", "X road");
        TestList(cli);
        cli.O_close_file();
    }
}

Build and run:

csc /r:tig.dll /r:record.dll OTest.cs OshrClient.cs OCustomer.cs
OTest

OCustomer.py:

import struct

class OCustomer:
    @staticmethod
    def format(id):
        return '%07d' % (id)
    @staticmethod
    def parse(id):
        return int(id)
    @staticmethod
    def pad(name):
        return name.ljust(32, ' ')
    def __init__(self, id = 0, name = '', address = ''):
        self.id = id
        self.name = name
        self.address = address
    def pack(self):
        return struct.pack('7s32s64s', bytes(OCustomer.format(self.id), 'iso-8859-1'), bytes(self.name.ljust(32,' '), 'iso-8859-1'), bytes(self.address.ljust(64,' '), 'iso-8859-1'))
    def unpack(self, blk):
        data = struct.unpack('7s32s64s', blk)
        self.id = OCustomer.parse(str(data[0], 'iso-8859-1'))
        self.name = str(data[1], 'iso-8859-1').rstrip(' \0')
        self.address = str(data[2], 'iso-8859-1').rstrip(' \0')

otest.py:

from OshrClient import OshrClient

from OCustomer import OCustomer

def dump(c):
    print('id=' + str(c.id))
    print('name=' + c.name)
    print('address=' + c.address)

def test_find_by_id(cli, id):
    c = OCustomer()
    stat, c = cli.o_find_by_id(OCustomer.format(id), c)
    if stat % 2 == 1:
        print('find by id: ' + str(id))
        dump(c)
    else:
        print('not found: ' + str(id))

def test_find_by_name(cli, name):
    c = OCustomer()
    stat, c = cli.o_find_by_name(OCustomer.pad(name), c)
    if stat % 2 == 1:
        print('find by name: ' + name)
        dump(c)
    else:
        print('not found: ' + name)

def test_list(cli):
    print('list')
    cli.o_list_start()
    while True:
        c = OCustomer()
        stat, c = cli.o_list_next(c)
        if stat % 2 != 1: break
        dump(c)

def test_add(cli, id, name, address):
    c = OCustomer(id, name, address)
    if cli.o_add(c) % 2 == 1:
        print('add succeded')
    else:
        print('add failed')

cli = OshrClient('arne4')
cli.o_open_file()
test_find_by_id(cli, 2)
test_find_by_id(cli, 9)
test_find_by_name(cli, 'B B')
test_find_by_name(cli, 'I I')
test_list(cli)
test_add(cli, 9, 'I I', 'I road 9')
test_list(cli)
test_add(cli, 0, 'X X', 'X road')
test_list(cli)
cli.o_close_file()

Run:

python otest.py

oCustomer.php:

<?php

class OCustomer {
    static function format($id) {
        return sprintf('%07d', $id);
    }
    static function parse($id) {
        return sscanf($id, '%07d')[0];
    }
    static function pad($name) {
        return str_pad($name, 32, ' ');
    }
    public $id;
    public $name;
    public $address;
    function __construct($id = 0, $name = '', $address = '') {
        $this->id = $id;
        $this->name = $name;
        $this->address = $address;
    }
    function pack() {
        return pack('a7a32a64', OCustomer::format($this->id), str_pad($this->name, 32, ' '), str_pad($this->address, 64, ' '));
    }
    function unpack($blk) {
        $this->id = OCustomer::parse(unpack('a7', $blk, 0)[1]);
        $this->name = trim(unpack('a32', $blk, 7)[1]);
        $this->address = trim(unpack('a64', $blk, 39)[1]);
    }
}

?>

otest.php:

<?php

include 'OCustomer.php';
include 'OshrClient.php';

function dump($c) {
    echo 'id=' . $c->id . "\r\n";
    echo 'name=' . $c->name . "\r\n";
    echo 'address=' . $c->address . "\r\n";
}

function test_find_by_id($cli, $id) {
    $c = new OCustomer();
    if($cli->o_find_by_id(OCustomer::format($id), $c) % 2 == 1) {
        echo 'find by id: ' . $id . "\r\n";
        dump($c);
    } else {
        echo 'not found: ' . $id . "\r\n";
    }
}

function test_find_by_name($cli, $name) {
    $c = new OCustomer();
    if($cli->o_find_by_name(OCustomer::pad($name), $c) % 2 == 1) {
        echo 'find by name: ' . $name . "\r\n";
        dump($c);
    } else {
        echo 'not found: ' . $name . "\r\n";
    }
}

function test_list($cli) {
    echo "list\r\n";
    $cli->o_list_start();
    $c = new OCustomer();
    while($cli->o_list_next($c) % 2 == 1) {
        dump($c);
    }
}

function test_add($cli, $id, $name, $address) {
    $c = new OCustomer($id, $name, $address);
    if($cli->o_add($c) % 2 == 1) {
        echo "add succeeded\r\n";
    } else {
        echo "add failed\r\n";
    }
}

$cli = new OshrClient('arne4');
$cli->o_open_file();
test_find_by_id($cli, 2);
test_find_by_id($cli, 10);
test_find_by_name($cli, 'B B');
test_find_by_name($cli, 'J J');
test_list($cli);
test_add($cli, 10, 'J J', 'J road 10');
test_list($cli);
test_add($cli, 0, 'X X', 'X road');
test_list($cli);
$cli->o_close_file()

?>

Run:

php otest.php

Web application:

o.jsp:

<html>
<head>
<title>Oshr test</title>
</head>
<body>
<h1>Oshr test</h1>
<h2>Find by id:</h2>
<form method="post" action="o_find_by_id.jsp">
Id: <input type="text" name="id">
<br>
<input type="submit" value="Find">
</form>
<h2>Find by name:</h2>
<form method="post" action="o_find_by_name.jsp">
Name: <input type="text" name="name">
<br>
<input type="submit" value="Find">
</form>
<h2>List all:</h2>
<form method="post" action="o_list.jsp">
<input type="submit" value="List">
</form>                                                                                                                         
<h2>Add one:</h2>
<form method="post" action="o_add.jsp">
Id: <input type="text" name="id">
<br>
Name: <input type="text" name="name">
<br>
Address: <input type="text" name="address">
<br>
<input type="submit" value="Add">
</form>
</body>
</html>

o_find_by_id.jsp:

<%@ page import="oshr.*" %>
<html>
<head>
<title>Find by id</title>
</head>
<body>
<h1>Find by id</h1>
<h2>Result:</h2>
<%
int id = Integer.parseInt(request.getParameter("id"));
synchronized(OshrClient.class) {
    OshrClient cli = new OshrClient("arne4");
    cli.o_open_file();
    OCustomer[] c = new OCustomer[1];
    c[0] = new OCustomer();
    if(cli.o_find_by_id(OCustomer.format(id), c) % 2 == 1) {
        out.println("id = " + c[0].getId() + "<br>");
        out.println("name = " + c[0].getName() + "<br>");
        out.println("address = " + c[0].getAddress());
    } else {
        out.println("Error: id " + id + " not found");
    }
    cli.o_close_file();
}
%>
</body>
</html>

o_find_by_name.jsp:

<%@ page import="oshr.*" %>
<html>
<head>
<title>Find by name</title>
</head>
<body>
<h1>Find by name</h1>
<h2>Result:</h2>
<%
String name = request.getParameter("name");
synchronized(OshrClient.class) {
    OshrClient cli = new OshrClient("arne4");
    cli.o_open_file();
    OCustomer[] c = new OCustomer[1];
    c[0] = new OCustomer();
    if(cli.o_find_by_name(OCustomer.pad(name), c) % 2 == 1) {
        out.println("id = " + c[0].getId() + "<br>");
        out.println("name = " + c[0].getName() + "<br>");
        out.println("address = " + c[0].getAddress());
    } else {
        out.println("Error: name " + name + " not found");
    }
    cli.o_close_file();
}
%>
</body>
</html>

o_list.jsp:

<%@ page import="oshr.*" %>
<html>
<head>
<title>List all</title>
</head>
<body>
<h1>List all</h1>
<h2>List:</h2>
<table border="1">
<%
synchronized(OshrClient.class) {
    OshrClient cli = new OshrClient("arne4");
    cli.o_open_file();
    cli.o_list_start();
    OCustomer[] c = new OCustomer[1];
    c[0] = new OCustomer();
    while(cli.o_list_next(c) % 2 == 1) {
        out.println("<tr>");
        out.println("<td>" + c[0].getId() + "</td>");
        out.println("<td>" + c[0].getName() + "</td>");
        out.println("<td>" + c[0].getAddress() + "</td>");
        out.println("</tr>");
    }
    cli.o_close_file();
}
%>
</table>
</body>
</html>

o_add.jsp:

<%@ page import="oshr.*" %>
<html>
<head>
<title>Add one</title>
</head>
<body>
<h1>Add one</h1>
<h2>Status:</h2>
<%
int id = Integer.parseInt(request.getParameter("id"));
String name = request.getParameter("name");
String address = request.getParameter("address");
synchronized(OshrClient.class) {
    OshrClient cli = new OshrClient("arne4");
    cli.o_open_file();
    OCustomer c = new OCustomer(id, name, address);
    if(cli.o_add(c) % 2 == 1) {
        out.println(id + " added");
    } else {
        out.println(id + " not added");
    }
    cli.o_close_file();
}
%>
</body>
</html>

o.php:

<html>
<head>
<title>Oshr test</title>
</head>
<body>
<h1>Oshr test</h1>
<h2>Find by id:</h2>
<form method="post" action="o_find_by_id.php">
Id: <input type="text" name="id">
<br>
<input type="submit" value="Find">
</form>
<h2>Find by name:</h2>
<form method="post" action="o_find_by_name.php">
Name: <input type="text" name="name">
<br>
<input type="submit" value="Find">
</form>
<h2>List all:</h2>
<form method="post" action="o_list.php">
<input type="submit" value="List">
</form>
<h2>Add one:</h2>
<form method="post" action="o_add.php">
Id: <input type="text" name="id">
<br>
Name: <input type="text" name="name">
<br>
Address: <input type="text" name="address">
<br>
<input type="submit" value="Add">
</form>
</body>
</html>

o_find_by_id.php:

<html>
<head>
<title>Find by id</title>
</head>
<body>
<h1>Find by id</h1>
<h2>Result:</h2>
<?php

include 'OCustomer.php';
include 'OshrClient.php';

$id = (int)$_POST['id'];
$sem = sem_get(30003);
sem_acquire($sem);
$cli = new OshrClient('arne4');
$cli->o_open_file();
$c = new OCustomer();
if($cli->o_find_by_id($id, $c) % 2 == 1) {
    echo 'id = ' . $c->id . "<br>";
    echo 'name = ' . $c->name . "<br>";
    echo 'address = ' . $c->address . "";
} else {
    echo "Error: id $id not found";
}
$cli->o_close_file();
sem_release($sem);

?>
</body>
</html>

o_find_by_name.php:

<html>
<head>
<title>Find by name</title>
</head>
<body>
<h1>Find by name</h1>
<h2>Result:</h2>
<?php

include 'OCustomer.php';
include 'OshrClient.php';

$name = $_POST['name'];
$cli = new OshrClient('arne4');
$sem = sem_get(30003);
sem_acquire($sem);
$cli->o_open_file();
$c = new OCustomer();
if($cli->o_find_by_name($name, $c) % 2 == 1) {
    echo 'id = ' . $c->id . "<br>";
    echo 'name = ' . $c->name . "<br>";
    echo 'address = ' . $c->address . "";
} else {
    echo "Error: name $name not found";
}
$cli->o_close_file();
sem_release($sem);

?>
</body>
</html>

o_list.php:

<html>
<head>
<title>List all</title>
</head>
<body>
<h1>List all</h1>
<h2>List:</h2>
<table border="1">
<?php

include 'OCustomer.php';
include 'OshrClient.php';

$sem = sem_get(30001);
sem_acquire($sem);
$cli = new OshrClient('arne4');
$cli->o_open_file();
$cli->o_list_start();
$c = new OCustomer();
while($cli->o_list_next($c) % 2 == 1) {
    echo '<tr>';
    echo '<td>' . $c->id . '</td>';
    echo '<td>' . $c->name . '</td>';
    echo '<td>' . $c->address . '</td>';
    echo '</tr>';
}
$cli->o_close_file();
sem_release($sem);

?>
</table>
</body>
</html>

o_add.php:

<html>
<head>
<title>Add one</title>
</head>
<body>
<h1>Add one</h1>
<h2>Status:</h2>
<?php

include 'OCustomer.php';
include 'OshrClient.php';

$id = (int)$_POST['id'];
$name = $_POST['name'];
$address = $_POST['address'];
$sem = sem_get(30001);
sem_acquire($sem);
$cli = new OshrClient('arne4');
$cli->o_open_file();
$c = new OCustomer($id, $name, $address);
if($cli->o_add($c) % 2 == 1) {                                                             
    echo "$id added";
} else {
    echo "$id not added";
}
$cli->o_close_file();
sem_release($sem);

?>
</body>
</html>

Article history:

Version Date Description
1.0 February 15th 2025 Initial version

Other articles:

See list of all articles here

Comments:

Please send comments to Arne Vajhøj