Cプログラムからopensource COBOLの呼び出しをした

# 概要

以前の[記事](https://qiita.com/nor51010/items/a4a4b40b96c58cb05d62)では、COBOLからCのプログラムを呼び出したので、今回は逆にCからCOBOLのプログラムを呼び出してみようと思います。

# opensource COBOLの呼び出し

### そもそもCOBOLの実行方法

opensource COBOLのプログラムはC言語に翻訳された上で、Cとしてコンパイルされています。
この際、SOモジュールとしたプログラムを実行する機能としてcobcrunが提供されています。
今回はこのcobcrunを参考にして、CからCOBOLを呼び出すプログラムを作成しました。

### cobcrunの処理

cobcrunの処理としてmainの中は以下のようになっています。

```c:cobcrun.c
int
main (int argc, char **argv)
{
	int pcl_return;
	
	union {
		int	(*func)();
		void	*func_void;
	} unifunc;
	
#ifdef	HAVE_SETLOCALE
	setlocale (LC_ALL, "");
#endif

	pcl_return = process_command_line (argc, argv);

	if (pcl_return != 99) {
		return pcl_return;
	}

	if (strlen (argv[1]) > 31) {
		fprintf (stderr, "Invalid PROGRAM name\n");
		return 1;
	}
	cob_init (argc - 1, &argv[1]);
	unifunc.func_void = cob_resolve (argv[1]);
	if (unifunc.func_void == NULL) {
		cob_call_error ();
	}
	cob_stop_run ( unifunc.func() );
}
```

基本的に必要な処理は以下の4点です。

- `cob_init (argc - 1, &argv[1]);`

libcobの初期化を行う関数です。
これをあらかじめ呼び出しておかないとCOBOLの呼び出しがエラーとなります。

- `unifunc.func_void = cob_resolve (argv[1]);`

COBOLのSOファイルを動的にロードしています。
`argv[1]`にはCOBOLのプログラムIDを入れます。

- `unifunc.func()`

ロードしたプログラムを実行します。返り値はINTです。

- `cob_stop_run ( unifunc.func() );`

COBOLのSTOPRUNにあたる終了処理です。
内部では`exit`システムコールを呼び出しているため、
プロセスごと終了してしまうので注意が必要です。

### COBOLの呼び出し

上記を踏まえて、COBOLを呼び出すプログラムを作成します。
といっても、上記必要な関数を呼び出すだけになります。

```c:cblcall.c
#include <stdio.h>
#include <libcob.h>

int
main(int argc, char **argv)
{
        int return_code;
        union {
                int     (*func)();
                void    *func_void;
        } unifunc;

        cob_init (0, NULL);

        unifunc.func_void = cob_resolve (argv[1]);
        if (unifunc.func_void == NULL) {
                cob_call_error ();
        }

        return_code = unifunc.func();

        cob_stop_run ( return_code );
}
```

これを、libcobをリンクさせてコンパイルしていきます。(-I, -Lは適宜設定してください)

```
gcc -I/usr/cobol/include -L/usr/cobol/lib -lcob -o cblcall cblcall.c
```

あとは、引数にCOBOLプログラム名称を設定して起動すると、COBOLを呼び出すことができます。
HELLO.cblを呼び出してみます。

```HELLO.cbl
       IDENTIFICATION              DIVISION.
       PROGRAM-ID.                 HELLO.
       DATA                        DIVISION.
       WORKING-STORAGE             SECTION.
       PROCEDURE                   DIVISION.
       MAIN-RTN.
           DISPLAY "HELLO WORLD!".
       MAIN-EXT.
           GOBACK.
```

```
[user@localhost test]$ ./cblcall HELLO
HELLO WORLD!
```

問題なく実行できています。
COBOL側でSTOP RUNを記述してしまうと、内部で`exit`システムコールが呼び出されてしまうため、
親プログラムに返らず終了してしまうので注意してください。

### 引数について

COBOLに渡す引数については、以前の記事のCOBOLから受け取る引数とは多少動作がことなります。

| COBOLデータ型|渡し方|Cデータ型|備考|
|:-----------------|------------------|------------------|------------------|
| 数値型(PIC 9)|参照渡し|char *|DISPLAY型の数値については、文字列で参照渡しをする必要がある|
| 文字列型(PIC X)|参照渡し|char *|サイズを合わせてスペース埋めをする必要がある|
|バイナリ型(COMP-X)|値渡し|short,int,long(COBOLのサイズによる)|ENDIANによって動きが異なる可能性あり|
|バイナリ型(COMP-X)|参照渡し|short *,int *,long *(COBOLのサイズによる)|ENDIANによって動きが異なる可能性あり|

COBOL側はLINKAGEに定義し、PROCEDUREのUSINGに渡し方を記載する形になります。

今回もintとchar *の値を連携してみようと思います。
以下、サンプルになります。

```c:cblcall2.c
#include <stdio.h>
#include <libcob.h>

int
main(int argc, char **argv)
{
        int return_code;
        union {
                int     (*func)();
                void    *func_void;
        } unifunc;

        int value1 = 11;
        char value2[2] = "11";
        char value3[10] = "HELLO!    ";

        cob_init (0, NULL);

        unifunc.func_void = cob_resolve (argv[1]);
        if (unifunc.func_void == NULL) {
                cob_call_error ();
        }

        return_code = unifunc.func(value1, value2, value3);

        cob_stop_run ( return_code );
}
```

```cobol:HELLO2
       IDENTIFICATION              DIVISION.
       PROGRAM-ID.                 HELLO2.
       DATA                        DIVISION.
       WORKING-STORAGE             SECTION.
       LINKAGE                     SECTION.
       01   VALUE1                 PIC 9(8) COMP-5.
       01   VALUE2                 PIC 9(2).
       01   VALUE3                 PIC X(10).
       PROCEDURE                   DIVISION
                                   USING BY VALUE     VALUE1
                                         BY REFERENCE VALUE2
                                         BY REFERENCE VALUE3.
       MAIN-RTN.
           DISPLAY "VALUE1:" VALUE1.
           DISPLAY "VALUE2:" VALUE2.
           DISPLAY "VALUE3:" VALUE3.
       MAIN-EXT.
           GOBACK.
```

```
[user@localhost test]$ ./cblcall2 HELLO2
VALUE1:00000011
VALUE2:11
VALUE3:HELLO!
```

ちょっと複雑にはなっていますが、無事に引数を渡すことができました。

# まとめ

COBOLからCを呼び出す場合より、opensource COBOLの内部仕様を確認する必要があり、多少手間はかかりますが、コード自体は単純な形式でCOBOLを呼び出すことができました。

これにより、COBOLで作成していた検索サブルーチンや、バッチ処理などを、Cで作成したプログラムから利用するといったことが可能になり、資産の有効活用ができるかもしれません。