opensource COBOLとCプログラムの連携

# 概要

opensource COBOLはCOBOLプログラムをC言語へと変換して、GCC等のCコンパイラでコンパイルし実行可能としています。
そのため、C言語で作られたアプリケーションとは親和性が高く、容易に連携させることができると想定されます。
今回はこれらの連携について試してみます。

# COBOLの外部アプリケーション呼び出し

### CALL文

そもそもCOBOLの外部アプリケーションで呼び出す場合には、`CALL`命令を利用します。
使い方は以下のような形です。

```cobol:CALL01.cbl
      ******************************************************************
       IDENTIFICATION              DIVISION.
      ******************************************************************
       PROGRAM-ID.                 CALL01.
       AUTHOR.                     nor51010.
      ******************************************************************
       ENVIRONMENT                 DIVISION.
      ******************************************************************
       DATA                        DIVISION.
      ******************************************************************
       WORKING-STORAGE             SECTION.
       01  F01                     PIC 9(6).
       01  RET                     PIC 9(4) COMP-X.
      ******************************************************************
       PROCEDURE                   DIVISION.
      ******************************************************************
       MAIN-RTN.
           MOVE 123456 TO F01.
           CALL "CALL02" USING  F01
                         GIVING RET.
           DISPLAY "RETURN-CODE:" RET.
       MAIN-EXT.
           STOP RUN.
```

`CALL`の次に呼び出すアプリケーション名を指定し、`USING`の後ろに引数を記載します。
また、`GIVING`で返り値を取得することができます。

引き続いて呼ばれる側です。呼ばれる側の例は以下のようになります。

```cobol:CALL02.cbl
      ******************************************************************
       IDENTIFICATION              DIVISION.
      ******************************************************************
       PROGRAM-ID.                 CALL02.
       AUTHOR.                     nor51010.
      ******************************************************************
       ENVIRONMENT                 DIVISION.
      ******************************************************************
       DATA                        DIVISION.
      ******************************************************************
       WORKING-STORAGE             SECTION.
      ******************************************************************
       LINKAGE                     SECTION.
       01  F01                     PIC 9(6).
      ******************************************************************
       PROCEDURE                   DIVISION USING F01.
      ******************************************************************
       MAIN-RTN.
           DISPLAY F01.
           MOVE 10 TO RETURN-CODE.
       MAIN-EXT.
           GOBACK.
```

呼び出される側では、引数としてもらう変数を`LINKAGE SECTION`に記載します。
また、`PROCEDURE DIVISION`の後ろに`USING`で受ける変数名を記載します。
これにより、呼び元のアプリケーションから引数の値を受け取れます。

また、`RETURN-CODE`に値を設定することで、呼び出し元のアプリケーションに返り値を渡すことができます。
この値は数値のみとなります。

### 参照渡し、値渡し

引数を渡すアプリケーションを作成する際、避けては通れない問題として、「参照渡し」、「値渡し」があります。
COBOLでは非常に単純化されてわかりやすくなっています。

`USING`の後ろに`BY VALUE`を指定していれば値渡し、省略されているか`BY REFERENCE`が指定されていれば参照渡しになります。
これらの指定は呼び出し元、呼び出し先で一致している必要があります。

```
      *    参照渡し
           CALL "CALL02" USING BY REFERENCE F01.
      *    値渡し
           CALL "CALL02" USING BY VALUE F01.
```

```
      *    参照受け取り
       PROCEDURE DIVISION USING BY REFERENCE F01.
      *    値受け取り
       PROCEDURE DIVISION USING BY VALUE F01.
```

これらは他のプログラミング言語の概念と同様と考えて問題ありません。

- 参照渡しイメージ

```
void
CALL01_ ()
{
        int f01, ret;
        f01 = 123456;
        ret = CALL02_ (&f01);
        printf ("%d\n", ret);
}

int
CALL02_ (int *f01)
{
        printf ("%d\n", *f01);
        return 10;
}
```

- 値渡しイメージ

```
void
CALL01 ()
{
	int f01, ret;
	f01 = 123456;
	ret = CALL02 (f01);
	printf ("%d\n", ret);
}

int
CALL02 (int f01)
{
	printf ("%d\n", f01);
	return 10;
}
```

※注意点：opensource COBOLでは値渡しを選択できるのは数値項目のみとなっています。
　　　　　文字列項目（X, N）は参照渡しのみが可能です。

# C言語との連携

### CアプリケーションのCALL

これまでを踏まえてC言語と連携してみます。まずは引数無しの連携として、COBOLからC言語の呼び出しを実装します。

COBOLアプリケーションは通常のCALL文で実装します。

```cobol:CALL03.cbl
      ******************************************************************
       IDENTIFICATION              DIVISION.
      ******************************************************************
       PROGRAM-ID.                 CALL03.
       AUTHOR.                     nor51010.
      ******************************************************************
       ENVIRONMENT                 DIVISION.
      ******************************************************************
       DATA                        DIVISION.
      ******************************************************************
       WORKING-STORAGE             SECTION.
      ******************************************************************
       PROCEDURE                   DIVISION
      ******************************************************************
       MAIN-RTN.
           CALL "cfunction".
       MAIN-EXT.
           GOBACK.
```

C言語側は該当の関数を実装するだけです。ここでファイル名は関数名と合わせておきます。

```c:cfunction.c
#include <stdio.h>

int cfunction ()
{
        printf ("cfunction called.\n");
        return 0;
}
```

あとはコンパイルして連携します。
連携方法としては動的ライブラリとして参照する場合、と静的リンクをしてしまう方法があります。

- 静的リンク

Cアプリケーションから.oファイルを作成し、COBOLのコンパイル時にリンクします。
cobcの引数に.oファイルを引き渡すことでリンクを行ってくれます。
※Cのコンパイル時には`-fPIC`が必要となります。

```
[user@localhost test]$ gcc -fPIC -c cfunction.c
[user@localhost test]$ cobc -x CALL03.cbl cfunction.o
[user@localhost test]$ ./CALL03
cfunction called.
```

- 動的リンク１

静的リンクではCアプリケーションが作り直しになる度、全コンパイルが必要となり手間がかかります。
opensource COBOLでは動的ライブラリの呼び出しもできるため、そちらも試してみます。

```
[user@localhost test]$ gcc --shared -fPIC -o cfunction.so cfunction.c
[user@localhost test]$ cobc src/CALL03.cbl
[user@localhost test]$ cobcrun CALL03
cfunction called.
```

ただし、この場合では、Cから作成した.soファイルの名前がファンクション名と異なると呼び出せずエラーとなります。
そのため、通常はリンクする必要があります。
`-l`オプションでリンクするためにCからコンパイルした動的ライブラリは先頭にlibをつける必要があります。

```
[user@localhost test]$ gcc --shared -fPIC -o libcfunction.so cfunction.c
[user@localhost test]$ cobc -L. -lcfunction src/CALL03.cbl
[user@localhost test]$ export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
[user@localhost test]$ cobcrun CALL03
cfunction called.
```

リンクさえしてしまえば、COBOLから簡単にCアプリケーションを呼び出せました。

### 引数の連携

次はCアプリケーションに引数を渡してみようと思います。
opensource COBOLからの引数は以下の対応表のようになります。
※()は代表的な型

| COBOLデータ型|渡し方|Cデータ型|
|:-----------------|------------------|------------------|
| 数値型(PIC 9)|値渡し|int|
| 文字列型(PIC X)|参照渡し|char *|
|バイナリ型(COMP-X)|値渡し|short,int,long(COBOLのサイズによる)|
|バイナリ型(COMP-X)|参照渡し|short *,int *,long *(COBOLのサイズによる)|

以上から、基本的に数値は値渡し、文字列は参照渡しとし、返り値または文字列領域に値を返すことで連携ができます。
幸い、COBOLは文字列から数値型への変換も容易なので、そのような返し方もできるでしょう。

注意点として、COBOLの文字列は、必ずしも末尾が`NULL`(`\0`)になっているとは限りません。
メモリ破壊などの要因になりますので、FILLERを追加するなど、何等かの手段で末尾が`LOW-VALUE`になるようにしたほうがよいです。

例は以下のようになります。

```cobol:CALL04.cbl
      ******************************************************************
       IDENTIFICATION              DIVISION.
      ******************************************************************
       PROGRAM-ID.                 CALL04.
       AUTHOR.                     nor51010.
      ******************************************************************
       ENVIRONMENT                 DIVISION.
      ******************************************************************
       DATA                        DIVISION.
      ******************************************************************
       WORKING-STORAGE             SECTION.
       01  F01                     PIC 9(03).
       01  G01.
           03  F02                 PIC X(20).
           03  FILLER              PIC X(1)   VALUE LOW-VALUE.
      ******************************************************************
       PROCEDURE                   DIVISION.
      ******************************************************************
       MAIN-RTN.
           MOVE 123 TO F01.
           MOVE "hello world!" TO F02.
           CALL "cfunction2" USING BY VALUE     F01
                                   BY REFERENCE F02.
       MAIN-EXT.
           GOBACK.
```

```c:cfunction2.c
#include <stdio.h>

int
cfunction2 (int f01, char *f02)
{
	printf ("cfunction2 called.\n");
	printf ("f01:%d\n", f01);
	printf ("f02:%s\n", f02);
	return 0;
}
```

実行例は次のとおりです。

```
[user@localhost test]$ gcc -shared -fPIC -o libcfunction2.so cfunction2.c
[user@localhost test]$ cobc -L. -lcfunction2 CALL04.cbl
[user@localhost test]$ cobcrun CALL04
cfunction2 called.
f01:123
f02:hello world!
```

正しくCアプリケーションに引数が渡せていることがわかります。

# 連携例

Cと連携できることはわかりましたが、これで何ができるかというのが重要かと思います。

この利点は、opensource COBOLで対応されていないシステムコールを呼び出したり、外部ライブラリとアクセスするためのラッパーを作ることで、オープン環境の便利機能をCOBOLから利用可能になるといった点です。
forkするアプリケーションを呼び出してCOBOLなのに並列処理をしてみたりと夢が広がりますが、まずは現実的な例で実現してみます。

### COBOLのログをsyslogに出してみる

COBOLからDISPLAYを使ったり、ファイルに書き出したりしてログを出力することはよくありますが、他のアプリケーションとログを一括で扱うためにはsyslogに書き出せると便利です。
このため、syslogに出力するCアプリケーションを作成し、ログの出力をそちらに任せてみます。

- Cプログラム

`syslog.h`を読み込み、COBOLから受け取った文字列を`syslog`用の関数に入れるだけのラッパーです。
syslog関数についての詳細は[マニュアル等](https://linuxjm.osdn.jp/html/LDP_man-pages/man3/syslog.3.html)を参照してください。
出力先は取り急ぎ`LOCAL3`としてます。

```c:csyslog.c
#include <syslog.h>

void c_openlog (const char *, int, int);
void c_syslog (int, const char *);
void c_closelog (void);

void
csyslog (const char *ident, int cobpriority, const char *output)
{
	int priority;

	if (cobpriority == 0) {
		priority = LOG_ERR;
	} else if (cobpriority == 0) {
		priority = LOG_INFO;
        } else {
		priority = LOG_DEBUG;
	}

	c_openlog (ident, LOG_PID, LOG_LOCAL3);
	c_syslog (priority, output);
	c_closelog ();
}

void
c_openlog (const char *ident, int option, int facility)
{
	openlog (ident, option, facility);
}

void
c_syslog (int priority, const char *output)
{
	syslog(priority, "%s\n", output); 
}

void
c_closelog (void)
{
	closelog ();
}
```

- COBOL

COBOLプログラムからはログのレベルと出力文字列を引き渡します。

```cobol:SYSLOG01.cbl
      ******************************************************************
       IDENTIFICATION              DIVISION.
      ******************************************************************
       PROGRAM-ID.                 SYSLOG01.
       AUTHOR.                     nor51010.
      ******************************************************************
       ENVIRONMENT                 DIVISION.
      ******************************************************************
       DATA                        DIVISION.
      ******************************************************************
       WORKING-STORAGE             SECTION.
       01  F01                     PIC 9(01).
       01  G01.
           03  F02                 PIC X(20).
           03  FILLER              PIC X(1)   VALUE LOW-VALUE.
      ******************************************************************
       PROCEDURE                   DIVISION.
      ******************************************************************
       MAIN-RTN.
           MOVE 0 TO F01.
           MOVE "ERROR MESSAGE." TO F02.
           CALL "csyslog"    USING BY REFERENCE "SYSLOG01"
                                   BY VALUE     F01
                                   BY REFERENCE F02.

           MOVE 1 TO F01.
           MOVE "INFO MESSAGE." TO F02.
           CALL "csyslog"    USING BY REFERENCE "SYSLOG01"
                                   BY VALUE     F01
                                   BY REFERENCE F02.

       MAIN-EXT.
           GOBACK.
```

実行する前にsyslogの設定ファイルを修正し、LOCAL3の出力を定義します。
渡しの環境で編集した例を記載します。
LOCAL3のログを/var/log/my.logに出力します。

```:/etc/rsyslog.conf
<前略>
#nor51010 add
local3.*                                                /var/log/my.log
```

実際に実行してみます。

```
[user@localhost test]$ gcc -shared -fPIC -o libcsyslog.so csyslog.c
[user@localhost test]$ cobc -L. -lcsyslog SYSLOG01.cbl
[user@localhost test]$ cobcrun SYSLOG01
```

```
[root@localhost log]# cat /var/log/my.log
Jul 31 11:38:58 localhost SYSLOG01[24882]: ERROR MESSAGE.
Jul 31 11:38:58 localhost SYSLOG01[24882]: INFO MESSAGE.
```

以上でCOBOLからsyslogへの出力を対応することができました。

# 最後に

opensource COBOLはその性質上、Cアプリケーションとの連携が簡単にできるため、オープン系の機能を利用するための拡張が容易にできることがわかりました。

クラウド向けのライブラリなどと連携することで、コンテナクラウドへの親和性が一気に高まると思いますので、いろいろできそうですね。

また、Cからopensource COBOLの呼び出しについてもそのうち触れたいと思います。