limitusus’s diary

主に技術のことを書きます

Perlからshared objectの関数を呼び出す

今回のお題

foo.so に含まれている関数 void func(void) をPerlから呼び出す。
プロトタイプ宣言の通り、引数は取らないし戻り値もない(あるいは見ない)。

やりかたはいくつかあって、

  • 普通ならXSモジュールを書いて対応する。
    • ただこのシンプルな目的のためにXSモジュールを書くのはいささか大袈裟すぎる。
  • もう少し簡単にやろうと思うと Inline::C を利用する。
    • これでも別に構わなかったのだけど、今回はこのPerlスクリプトをほぼ同時に並列起動する可能性があり、コンパイルが多重に走るのを避けたい。

ということで、ちょっと珍しいアプローチとしてXSモジュールのベースに使われているDynaLoaderモジュールを直接使ってみることにした。

DynaLoaderとは?

Perlで昔からあるコアモジュールで、*.soファイルをロードし、その中のシンボルをモジュール名前空間にバインドする処理をしてくれる。

作者はTim Bunceとなっている。もちろん他にも何人かが関わっているはずだけど。1994年製。NYTProfなどの作者として有名で、今もPerlで活動している。

ただし利用インターフェースはかなり古いもので、ドキュメントも素気ないしネット上に転がっているサンプルも少なく、ちょっととっつきにくい。マサカリを持っていそうな人が書いたコードは転がっているが、今回のような用途にはあまり役立ちそうになかった。

解決

perldocを見ながら実際に書いてみた。実際のところどういう作法が正しいのかはっきりしなかったが、特に問題なく動いたのでおそらくこんな感じでよい。

エッセンスだけを抜き出すと以下のように書く。

package ModCaller;

 use DynaLoader;
 our @ISA = qw(DynaLoader);

my $libref = DynaLoader::dl_load_file("callee.so", 0x01);
my $symref = DynaLoader::dl_find_symbol($libref, "show_message");
my $show_mes_ref = DynaLoader::dl_install_xsub("show_message", $symref, "callee.c");
  • まず dl_load_file でshared objectのファイルを読み込む。第2引数の 0x01 はこの例では 0x00 でもよい。他の dl_load_file のために必要であればリンクさせるためのフラグ。
  • 次に dl_symbol でライブラリ中のシンボルを探しに行く。シンボルを見付けられればそのポインタが返ってくる。
  • Cならこれを呼べばいいが、Perlではそうもいかないのでこれを名前空間にバインドする。これが dl_install_xsub で、第3引数(省略可能)には例外発生時にメッセージに出したいファイル名を渡すことができる。

この処理によって ModCaller::show_message が定義される。

完全なサンプルコード

エラー処理も含めた完全なサンプルコードを下記に掲載する。

GitHub上にも置いておいた。https://github.com/limitusus/snippet/tree/master/perl-so

callee.c

callee.so にコンパイルされるCコード

#include <stdio.h>

int show_message(void) {
    setbuf(stdout, 0);
    printf("this is a test message\n");
    return 0;
}
Makefile

callee.c をコンパイルして callee.so を生成するように書いてある

CFLAGS=-fPIC

.PHONY: all clean

all: callee.so

callee.so: callee.o
	$(CC) -shared -fPIC -o $@ $^

callee.o: callee.c

clean:
	$(RM) -f *.o callee.so *~
caller.pl

callee.so に定義されている関数を呼び出すだけのシンプルなコード

#!/usr/bin/env perl

use strict;
use warnings;

use 5.008;

use ModCaller;

ModCaller::show_message();
ModCaller.pm

今回の目玉。名前空間に callee.so の show_message 関数を導入して Export する。

package ModCaller;

use strict;
use warnings;

use DynaLoader;
use Exporter;

our @ISA = qw(DynaLoader Exporter);
our @EXPORT = ();
our @EXPORT_OK = qw/show_message/;

our $VERSION = "1.0";

my $libref = DynaLoader::dl_load_file("callee.so", 0x01);
if (!$libref) {
    die "failed to load shared object";
}
my $symref = DynaLoader::dl_find_symbol($libref, "show_message");
if (!$symref) {
    die "failed to find symbol";
}
my $show_mes_ref = DynaLoader::dl_install_xsub("show_message", $symref, "callee.c");
if (!$show_mes_ref) {
    die "failed to install xsub";
}

1;

もっといい書き方があるのかもしれないけど、習作ということで。