Perl - XSでDLLを呼び出す(その9)

今回は複数の戻り値を返すサブルーチンを実装する。

複数の戻り値を持つサブルーチンの書き方

実装に入る前に、戻り値が複数存在するサブルーチンの書式を概ね把握して置こう。
その書式は以下のようになる。

void
<サブルーチン名>(<引き数名列>)
INPUT:
    <引き数のタイプ> <引き数名>;
            :
            :
PREINIT:
    <内部変数定義>
INIT:
    <初期化コード>
PPCODE:
    <処理コード>

戻り値が1つだけのサブルーチンとは以下の点が異なる。

  • 戻り値のタイプは常に便宜上voidとなる。
  • CODEフィールドの変わりにPPCODEフィールドを使用する。
  • OUTPUTフィールドは使用しない。

では早速、サブルーチンを実装して見よう。

一度に商と余を求めて結果を返す

引き数で指定された2つの実数で商と余を求め、その結果を返すサブルーチンをExample3.xsに追加で実装する。

void
div_num(num, deno)
INPUT:
    NV num;
    NV deno;
PREINIT:
    double n, r;
PPCODE:
    n = floor(num / deno);
    r = num - deno * n;
    XPUSHs(sv_2mortal(newSVnv((NV)n)));
    XPUSHs(sv_2mortal(newSVnv((NV)r)));

newSVnvマクロは指定した実数で初期化された新規スカラー値(SV:Scalar Value)を生成する。
sv_2mortalマクロは指定したスカラー値が未使用になった(参照カウンタがゼロになった)場合、ガベージコレクションの(mortal:死すべき)対象となるように設定する。
XPUSHsマクロはスタックに指定したスカラー値を積むためのもので、ここでは戻り値を積むために使用している。

テストのために以下のスクリプトを "c:\xs\Example3\TestEx3-3.pl" として保存する。

use ExtUtils::testlib;
use Example3;

print join(", ", Example3::div_num(100, 9)) . "\n";
print join(", ", Example3::div_num(123.456, 7.8)) . "\n";
eval{Example3::div_num(9.01)};
print $@;
eval{Example3::div_num(2, 3, 4)};
print $@;

上記を実行した結果は以下の通りである。

11, 1
15, 6.456
Usage: Example3::div_num(num, deno) at TestEx3-3.pl line 6.
Usage: Example3::div_num(num, deno) at TestEx3-3.pl line 8.

Cファイルの内容はどうだろうか。

XS(XS_Example3_div_num)
{
    dXSARGS;
    if (items != 2)
        Perl_croak(aTHX_ "Usage: Example3::div_num(num, deno)");
    SP -= items;
    {
        NV      num = (NV)SvNV(ST(0));
        NV      deno = (NV)SvNV(ST(1));
#line 41 "Example3.xs"
        double n, r;
#line 177 "Example3.c"
#line 43 "Example3.xs"
        n = floor(num / deno);
        r = num - deno * n;
        XPUSHs(sv_2mortal(newSVnv((NV)n)));
        XPUSHs(sv_2mortal(newSVnv((NV)r)));
#line 183 "Example3.c"
        PUTBACK;
        return;
    }
}

今まで目にしたdXSTARG、XSprePUSH、XSRETURNマクロが見当たらない。
その代わり、"SP -= items;" 構文とPUTBACKマクロが現われた。
"SP -= items;" 構文により、スタックから引き数を下ろした状態となる。
PUTBACKマクロにより、XPUSHsマクロでスタックに積んだ戻り値を決定する。

渡された引き数をそのまま戻す

渡された引き数をそのまま戻り値として返すサブルーチンをExample3.xsに追加で実装する。

void
echo(...)
PREINIT:
    int i;
PPCODE:
    for (i = 0; i < items; i++) {
        XPUSHs(sv_2mortal(newSVsv(ST(i))));
    }

newSVsvマクロは指定したスカラー値の複製を新規に生成する。

テストのために以下のスクリプトを "c:\xs\Example3\TestEx3-4.pl" として保存する。

use ExtUtils::testlib;
use Example3;

$, = " ";
print Example3::echo(1, 2, 3, "\n");
print Example3::echo(1.1, 2.2, 3.3, "\n");
print Example3::echo("hello", "world", "\n");

上記を実行した結果は以下の通りである。

1 2 3
1.1 2.2 3.3
hello world

Cファイルの内容はどうだろうか。

XS(XS_Example3_echo)
{
    dXSARGS;
   PERL_UNUSED_VAR(ax); /* -Wall */
    SP -= items;
    {
#line 51 "Example3.xs"
        int i;
#line 198 "Example3.c"
#line 53 "Example3.xs"
        for (i = 0; i < items; i++) {
            XPUSHs(sv_2mortal(newSVsv(ST(i))));
        }
#line 203 "Example3.c"
        PUTBACK;
        return;
    }
}

"PERL_UNUSED_VAR(ax);" なる不思議な構文が現われた。
そのコメントから推察すると、Perlの "-Wall" オプションにより警告が表示されないようにするためではないだろうか。


次回は引き数の内容を変更して返すサブルーチンを実装して見ようと思う。