[05] C言語でコンソールから関数を呼び出す機構の振り返り ... コンソールからの関数呼び出し実行


|本シリーズのトップページ|
|:--|
|https://qiita.com/robozushi10/items/fc185b5da0509b9a631f|


# はじめに

本記事の趣旨は「[01 概要](https://qiita.com/robozushi10/items/40b3fc1791a1bab51e4a)」に記しているが、
13年前(=2008年) に職場のプログラムの挙動をマネて実装した
「コンソールから関数を呼び出す機構」の振り返りである.

本項では次の (5)(6)(7) について記す.

>(5) (4) のヘッダファイルを include して、コンソールから関数呼び出し機能も持つコードも含めてリンクする
　⬇️ 
(6) 作成された実行ファイル a.out を実行する
　⬇️ 
(7) a.out の stdin から、公開関数 (static 関数以外) とその引数を入力する
　⬇️ 

# 詳細

## (5) について

下記については次の通りである.

>(5) (4) のヘッダファイルを include して、コンソールから関数呼び出し機能も持つコードも含めてリンクする

次のようにコンパイルとリンクをして、a.out を作成する

Warning が結構出てしまうので、Warning をビルドエラー扱いにしていると動かない.
当時の職場では、静的テストの対象 (C++test) から同 Warning は検出対象外にされていたのだと思う.

```bash
$ gcc -g cfn_invoke.c priv_cfn_invoke.h test_driver.o
```

　

### priv_cfn_invoke.h 

次の🌟の通り「[(4) readelf -s](https://qiita.com/robozushi10/items/e3d7bba126d438e5cbfd)」で生成した `cfn.extern.h` と `cfn.tbl.h` を include している.

```c
略
/*========*/
/* extern */
/*========*/
#include "cfn.extern.h" 🌟

/*==========*/
/* function */
/*==========*/

static fn_tbl_t
g_fn_tbl[] =
{
#include "cfn.tbl.h" 🌟
};

#endif /* __CFN_INTERP_H__ */
```

### cfn_invoke.c

コンソールからの入力を待受けるプログラム.
`parser_start` の中で、コンソール入力された文字列の解析 と 関数呼び出しをしている[^1]

```c
略
#include "priv_cfn_invoke.h"
略

/* 外部提供I/F */
int
Invoke_from_stdin(void)
{
    parser_start(); //💥
    return 0;
}
```

入力文字列を解析して、改行を区切りにして関数呼び出し🛑をする

```c
/* パーサスタート */
static void
parser_start(void) //💥
{
    enum      { OK, NG };
    e_token   token  = TOK_token_unknown;
    e_atype   type   = TYPE_atype_unknown;
    int       retval = 0;
    int       argc   = 0;
    ainfo_t   alist[MAXARG+1];
    int       mode   = NORMAL_MODE;
    char      word[MAXWORD];

    /* ignoresig(); */

    while(1)
    {
        switch(token = do_lexer(word, &type, mode)) /* lexer(字句解析器)コール */
        {
略
        case TOK_NL:    /* 改行 */
            if((argc == 0) && (token != TOK_NL))
            {
                WARN("Illegal sequence\n");
            }
            else if(argc > 0)
            {
                retval = invoke(argc, &alist[0]);🛑
            }
略
} // parser_start(void) の終了
```

以下は、上の invoke関数🛑の中身である.

```c
/*
 * 関数ポインタと関数の引数を取得し，引数の個数に応じた
 * 関数をコールする
 */
static int
invoke(int argc, ainfo_t * alist) 
{
    enum   {not_found = -1};
    int    ret       = 0;
    int    (*func)() = NULL;
    void * al[MAXARG];

    memset(al, 0, sizeof(void *) * MAXARG);

    dbg_dump_args(argc, alist); /* debug */

    /* シンボル(関数)名からアドレスを取得する */
    func = conv_sym_2_addr(alist[0].sym); //🌟 "piyo" と入力されたら、&piyo を返す関数
    if(func == NULL)
    {
        WARN("not found %s\n", alist[0].sym);
        return not_found;
    }

    /* 引数(alist)の型変換を行い，alに格納する */
    dym_cast_args (argc, alist, al); //

    /* 引数の個数別に応じた関数をコールする */
    ret = invoke_by_argc(func, al, argc); //🌟 内部では piyo (al[1], al[2]) として関数呼び出しをしている.

    return ret;
}
```


## (6) について

下記については次の通りである.

> 作成された実行ファイル a.out を実行する

a.out を実行すると関数入力モードになる.
なお、実際のプログラムでは、stdin の待受けは別スレッドにしている.

```
$ ./a.out
```

## (7) について

下記については次の通りである.

>(7) a.out の stdin から、公開関数 (static 関数以外) とその引数を入力する

実行例である.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246056/99eb42cc-ac19-2a66-68f2-0a9719744e1d.png)


　

以上.

[^1]: parser という名称なので、外部関数の呼び出しは別関数の方が良いだろう と今見ると思う