mfi.sub.jp

GAWK 4.2.X DLLを作成する

GAWK4 の DLL(Dynamic Link Library) (呼称 extension) を作成すると、高速な関数を使用出来たり、GAWK の組み込み関数では不可能なことを実現できたりします。実力さえあれば、配慮の行き届いたオリジナルの関数を自由に追加していくことができるのです。
DLL の作成には C言語 や C++ を用いて、ソースを作成し、コンパイル、リンクを行いますが、まずは、DLL を使うところから紹介しましょう。
ここで使用するGAWKは GAWK4.2.1 for win32 です。


まずは付属の DLL を使用してみる

環境変数 AWKLIBPATH を設定する

これが設定されていなければ、DLL は使用できません。
AWKLIBPATH には、.dll ファイルが実際に収められているフォルダを設定します。

Windows のシステム環境変数に AWKLIBPATH を新規に追加して、絶対パスを設定。

コントロールパネル→システム→システムの詳細設定→環境変数

システム環境変数→新規

変数名 AWKLIBPATH
変数値 C:\gawk\lib\gawk (など DLL が入っているフォルダの絶対パス)

パスが登録されると GAWK4 は DLL ファイルの位置を探せるようになります。

DLL を動作させる (time.dll)

付属の DLL にはいくつかありますが、ここでは、ミリ秒を計測できる time.dll にある gettimeofday() を使います。ちなみに GAWK 組み込みの時間計測関数 systime() は秒単位です。

time.dll を使用するためには、AWKスクリプト先頭で以下のように書きます。

@load "time";

これで、gettimeofday() を printf() 等の組み込み関数と同じように使えるようになります。

1: # awk_dll_time.awk;time.dllを使用する 2: @load "time"; 3: BEGIN { 4: start = gettimeofday(); 5: for (i = 0; i < 1000000; i++) a += 2; 6: stop = gettimeofday(); 7: 8: print "a = " a; 9: printf("elapsed_time = %d(ms)", (stop - start) * 1000); 10: }

実行結果

dll_dev1.jpg

組み込み関数 systime() では測定できなかった 1 秒未満が計測可能になる

サクラエディタでコマンドプロンプトからの出力を取得しています。
(GAWK サクラエディタで快適に使う ページ参照)


DLL の作成準備

準備物一覧

  • 1 サクラエディタ ver 2.3.2.0
  • 2 GAWK4.2.1 for win32 のソースとバイナリ
  • 3 MinGW (gcc) のインストールとパスの設定
  • 4 DLL 構築自動化のためのバッチファイル
  • 5 バッチファイルを起動するサクラエディタのマクロ

  • 1 サクラエディタ ver 2.3.2.0

    https://sakura-editor.github.io/
    「最新バイナリ+関連ファイル簡単GET」SakuraDown がお勧めです。
    インストーラーパッケージにすると設定ファイルやマクロフォルダがややこしいです。

    2 GAWK4.2.1 for win32 のバイナリとソース

    著作者 Eli Zaretskii さん (SOURCEFORGE ezwinports) のWindows移植版です。
    https://sourceforge.net/projects/ezwinports/files/
    gawk-4.2.1-w32のバイナリ(2.4MB)
    gawk-4.2.1-w32のソース(6.0MB)

    3 MinGW (gcc) のインストールとパスの設定

    Minimalist GNU for Windows
    http://www.mingw.org/
    ホームページより「Downloads」→「mingw-get-setup.exe」を選択してインストールとパスの設定を行い、gccが使えるようにしてください。
    MinGW インストールに関してはここでは踏み込みません。

    4 DLL 自動構築のためのバッチファイル (DLL_CREATE.BAT)

    1 : REM DLL_CREATE.BAT 2 : REM for extension of GAWK4.2.X for win32 3 : @echo off 4 : pushd %0\.. 5 : setlocal 6 : set CNAME=%1 7 : mingw32-gcc -c %CNAME%.c 8 : mingw32-gcc -shared -o %CNAME%.dll %CNAME%.o 9 : copy %CNAME%.dll %AWKLIBPATH% 10 : del %CNAME%.o 11 : del %CNAME%.dll 12 : endlocal

    ★このファイルをGAWK4.2.1 for win32 ソースのディレクトリ直下 (gawkapi.h があるところ) に「DLL_CREATE.BAT」と名前を付けて保存する


    6行目 「set CNAME=%1」
    %1 にはサクラエディタで編集中のDLLソースファイル名(拡張子無し)が入ります。

    7行目 「mingw32-gcc -c %CNAME%.c」
    DLL ソースをコンパイルして、同ディレクトリに同名のオブジェクト(中間)ファイルを作成します。ファイル名は「%CNAME%.o」です。

    8行目 「mingw32-gcc -shared -o %CNAME%.dll %CNAME%.o」
    前行で作成された「%CNAME%.o」からリンク作業を経て、同ディレクトリに同名の DLL ファイルを作成します。

    9行目 「copy %CNAME%.dll %AWKLIBPATH%」
    システム環境変数 AWKLIBPATH (.dllファイルの格納場所) へ新しく作成したdllを上書きコピーします。上書き時に確認画面が出るようであれば「/Y」オプションを使います。
    前行で直接 AWKLIBPATH に書き込むことも可能ですが、筆者は複数ディレクトリに複製/同期するため、一旦 GAWK4 のディレクトリに DLL を作成し、COPY するようにしています。


    5 バッチファイルを起動するサクラエディタのマクロ(create_dll.vbs)

    1 : 'サクラマクロ create_dll.vbs 2 : Dim scmd, spath, smpath, sfilename, sextention, stmp, smsg, ret 3 : 4 : spath = Editor.GetFilename 'FullPath 5 : stmp = Mid(spath, InstrRev(spath, "\") + 1) 'FileName+Extention 6 : smpath = Mid(spath, 1, Len(spath) - Len(stmp)) 'MidPath 7 : sfilename = Mid(stmp, 1, Instr(stmp, ".") - 1) 'FileName 8 : sextention = Mid(spath, InstrRev(spath, ".") + 1) 'Extention 9 : smsg = "FILE_PATH = " & spath & vbCrLf & stmp & " から " & _ 10 : sfilename & ".dll を作成しますか?" 11 : If Lcase(sextention) = "c" Then 12 : ret = MsgBox(smsg, vbyesno, "DLL_CREATE.BAT") 13 : If ret = vbYes Then 'vbYes=6 14 : Editor.FileSave 15 : scmd = "cmd.exe /c " & smpath & "dll_create.bat " & sfilename 16 : Editor.ExecCommand scmd, 1 'redirect output window 17 : Editor.ActivateWinOutput 18 : End If 19 : End If

    編集中のCソース(DLL)から自身のフルパスを取得し、ディレクトリまでのパス、ファイル名、拡張子をそれぞれ変数に格納します。これを用いてメッセージボックスとコマンドライン(バッチファイルに引数を渡す)を作成し、コマンドラインを実行、結果をアウトプットウィンドウに表示します。

    このマクロをサクラエディタに登録して、コンテキストメニュー(右クリックメニュー)に追加すると、DLL ソースを編集、DLL を構築、GAWK4スクリプトで実行/確認という一連の作業を一元的に行えるようになります。(GAWK サクラエディタで快適に使う ページ参照)

    サクラエディタにマクロを登録する方法

    サクラエディタディレクトリの「macros」にcreate_dll.vbsをコピー
    設定→共通設定→マクロ(タブ)
    「マクロの一覧」空いているところを選択→「Id」が表示される
    「file」の三角ボタン(ドロップリスト)からcreate_dll.vbsを選択
    「名前(N)」わかりやすい名前をつける
    「マクロを実行するたびに~」チェックボックスにチェック
    「キャンセル待ち時間」60秒に設定
    「設定」ボタンを押す
    「マクロの一覧」4列目「実行時~」が「on」になっていることを確認

    コンテキストメニュー(右クリックメニュー)への登録方法

    設定→共通設定→カスタムメニュー(タブ)
    「種別(K)」ドロップダウンリストから「外部マクロ」を選択
    「選択(C)」が「右クリックメニュー」となっているか確認
    「機能」から先に登録したマクロ名を選択
    「→(A)」ボタンで「メニュー(M)」にマクロ名を移動
    「↓(O)」ボタンで適当な位置に配置する
    「OK」ボタンで登録完了

    DLL のソースを書く前に

    お手本に習う (ordchr.c)

    GAWK4.2.1 for win32 のソースの「extension」フォルダにある「ordchr.c」です。
    ord() は文字(アルファベット等の1文字)を数値(ASCII)に変換し、chr() は数値を文字に変換します。日本語のひらがなや漢字などの2byte文字には対応してはいません。GAWK にはこれらの関数がありませんでしたが、組み込み関数を駆使して同じ結果を導出するユーザー定義関数は作れました。重い処理になってしまいますが。

    1~25行目までは宣言部とでも言いましょうか、このままコピーして使い回せます。ちょっと変わった関数を使いたい場合など、C (gcc) のライブラリからヘッダファイルをインクルードしたりします。

    29~44行目までが関数 ord() の、48~71行目までが chr() の実装部分です。
    それぞれ do_ord() do_chr() と別名になっていますが、以下の2つで解決します。

    73~76行目までが GAWK 本体にこの DLL が内包する GAWK で使用可能な関数の情報を伝える「配列の配列」です。この配列(関数テーブル)は必ず必要になります。

    80行目は GAWK が DLL をロードする際に上記テーブルを読み込むためのマクロ関数です。関数テーブルの記述の違いによるバージョンチェックもしています。

    1 : #ifdef HAVE_CONFIG_H 2 : #include <config.h> 3 : #endif 4 : 5 : #include <stdio.h> 6 : #include <assert.h> 7 : #include <stdlib.h> 8 : #include <string.h> 9 : #include <unistd.h> 10 : 11 : #include <sys/types.h> 12 : #include <sys/stat.h> 13 : 14 : #include "gawkapi.h" 15 : 16 : #include "gettext.h" 17 : #define _(msgid) gettext(msgid) 18 : #define N_(msgid) msgid 19 : 20 : static const gawk_api_t *api; /* for convenience macros to work */ 21 : static awk_ext_id_t ext_id; 22 : static const char *ext_version = "ordchr extension: version 1.0"; 23 : static awk_bool_t (*init_func)(void) = NULL; 24 : 25 : int plugin_is_GPL_compatible; 26 : 27 : /* do_ord --- return numeric value of first char of string */ 28 : 29 : static awk_value_t * 30 : do_ord(int nargs, awk_value_t *result, struct awk_ext_func *unused) 31 : { 32 : awk_value_t str; 33 : double ret = -1; 34 : 35 : assert(result != NULL); 36 : 37 : if (get_argument(0, AWK_STRING, & str)) { 38 : ret = str.str_value.str[0]; 39 : } else if (do_lint) 40 : lintwarn(ext_id, _("ord: called with inappropriate argument(s)")); 41 : 42 : /* Set the return value */ 43 : return make_number(ret, result); 44 : } 45 : 46 : /* do_chr --- turn numeric value into a string */ 47 : 48 : static awk_value_t * 49 : do_chr(int nargs, awk_value_t *result, struct awk_ext_func *unused) 50 : { 51 : awk_value_t num; 52 : unsigned int ret = 0; 53 : double val = 0.0; 54 : char str[2]; 55 : 56 : str[0] = str[1] = '\0'; 57 : 58 : assert(result != NULL); 59 : 60 : if (get_argument(0, AWK_NUMBER, & num)) { 61 : val = num.num_value; 62 : ret = val; /* convert to int */ 63 : ret &= 0xff; 64 : str[0] = ret; 65 : str[1] = '\0'; 66 : } else if (do_lint) 67 : lintwarn(ext_id, _("chr: called with inappropriate argument(s)")); 68 : 69 : /* Set the return value */ 70 : return make_const_string(str, 1, result); 71 : } 72 : 73 : static awk_ext_func_t func_table[] = { 74 : { "ord", do_ord, 1, 1, awk_false, NULL }, 75 : { "chr", do_chr, 1, 1, awk_false, NULL }, 76 : }; 77 : 78 : /* define the dl_load function using the boilerplate macro */ 79 : 80 : dl_load_func(func_table, ord_chr, "")

    ord() の内幕をかんたんなフローにしてみました

    GAWK側では次のように使います

     n = ord("a");

    結果 n には 97(十進数) が代入されます。

    GAWKAPI側の処理
    1 ord()が使用された
    2 引数は1つ (nargs = 1)
    3 引数は文字列型
    4 引数は "a"、サイズは 1byte
    5 情報を一塊にして(api_set_argument) ordchr.dll の do_ord()を呼ぶ

    DLL側の処理
    1 GAWKAPI から do_ord() が呼ばれる
    2 do_ord()を実行
    3 処理に必要な変数を準備 (32-33行)
    4 アサーション(実行時診断) (35行)
    5 ★引数 a を得る...get_argument(0, AWK_STRING, & str) (37行)
     GAWK_APIが提供するGAWK側の実引数を取得する関数で次の引数を持つ
     取得する引数の番号 0→第一引数 1→第二引数 2→第三引数
     GAWK側で入力を期待される引数の型 AWK_STRING、AWK_NUMBER など
     引数(データ)の格納場所(アドレス)の登録 (3にて str を確保済)
     ...  str.str_value.str で、引数の実体 "a" にアクセスできるようになる
     ...  (3にて確保したstrは先頭のstr)
     (DLL 側に実体 "a" をコピーしたわけではない)
     参考:構造体  str_value には2つのメンバが存在する
         文字列へのポインタ   *str
         文字列の長さ(バイト)   len
    6 文字 a を数値 97 に変換し、ret に格納 (char型からdouble型にキャスト) (38行)
    7 get_argument()が偽の場合 警告処理(--lint オプション使用時のみ) (39-40行)
    8 結果(数値)を GAWKAPI に返す (ERROR時は -1 を返す line33) (43行)
       参考:DLL から GAWKAPI に数値や文字列を返す作法は決まっている
       Constructor Functions (>User's Guide)
       数 値:[return] make_number(num, result);
       文字列:[return] make_const_string(str, len, result);

    極論ですが、この中で頭を使うところは、6 の部分だけです。


    自作の DLL を作る

    自作のDLLソースを書く

    ordchr.c の do_ord() を雛形として 文字列の長さ(バイト)を返すソースを書きます。

    1 : static awk_value_t * 2 : do_lengthb(int nargs, awk_value_t *result, struct awk_ext_func *unused) 3 : { 4 : awk_value_t snz; 5 : double ret = -1; 6 : 7 : assert(result != NULL); 8 : 9 : if (get_argument(0, AWK_STRING, & snz)) { 10 : ret = snz.str_value.len; 11 : } else if (do_lint) 12 : lintwarn(ext_id, _("lengthb: called with inappropriate argument(s)")); 13 : 14 : /* Set the return value */ 15 : return make_number(ret, result); 16 : } 17 : 18 : static awk_ext_func_t func_table[] = { 19 : { "lengthb", do_lengthb, 1, 1, awk_false, NULL }, 20 : }; 21 : 22 : /* define the dl_load function using the boilerplate macro */ 23 : 24 : dl_load_func(func_table, lengthb, "")

    変更箇所

    1. 02行 関数名 do_ord → do_lengthb
    2. 04行 変数の名前 str → snz
    3. 09行 同様
    4. 10行 同様, 構造体のメンバ *str → len (lenは*strのバイト数を保持)
    5. 12行 関数名 ord → lengthb
    6. 19行 関数名 ord → lengthb, 2行目と同様
    7. 24行 extensionの名前 ord_chr → lengthb

        このソースに、お手本の ordchr.c より宣言部(1~25行目)をコピーして、22行目の「ordchr extension」を「lengthb extension」に書き換えてください。

        このソースファイルを、GAWK4.2.1 for win32 ソースのディレクトリ直下 (gawkapi.h があるところ) に「lengthb.c」と名前を付けて保存します。

        コンパイル前に、lengthb.c と DLL_CREATE.BAT が共にGAWK4.2.1 for win32 「ソース」のディレクトリ直下に存在しているか、ご確認ください。


        自作のDLLをコンパイル/リンクする

        上述の「lengthb.c」をサクラエディタで開き、コンテキストメニュー(右クリックメニュー)を表示
        dll_dev2.jpg
        DLL_CREATE.BAT (create_dll.vbs に割り当てたマクロ名) を選択
        dll_dev3.jpg
        メッセージボックス表示 「はい(Y)」をクリック
        dll_dev4.jpg
        gccのエラー/警告無し、Dllコピー完了
        dll_dev5.jpg
        AWKLIBPATH のディレクトリ (筆者の環境ではRAMディスク上の Y:\) に lengthb.dll が作成されている

        DLLの動作確認については、こちらのページで別添の関数とともに行っています。