憂鬱な午後のひととき


10月31日_

LD_PRELOADを使って共有ライブラリを入れ換える

『Binary Hack』の復習. read, writeにwaitを入れて遅いIOを作ってみる.

// slowio.c
#define _GNU_SOURCE
#include <dlfcn.h>
#include <unistd.h>

ssize_t (*write_org)(int fd, const void *buf, size_t count);
ssize_t (*read_org)(int fd, void *buf, size_t count);

void __attribute__((constructor))
init_open()
{
    write_org = dlsym(RTLD_NEXT, "write");
    read_org = dlsym(RTLD_NEXT, "read");
}

ssize_t write(int fd, const void *buf, size_t count)
{
    usleep(100 * 1000);
    return write_org(fd, buf, count);
}

ssize_t read(int fd, void *buf, size_t count)
{
    usleep(100 * 1000);
    return read_org(fd, buf, count);
}

これをbuildして共有ライブラリを作る.

// Makefile
slowio.so: slowio.c
    gcc -o $@ $< -shared -fPIC -ldl -Wall -Wextra

readを使うプログラムを適当に作る.

// test_slowio.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
    int fd = open("test_slowio.c", O_RDONLY);
    if (fd < 0) {
        perror("open");
        return 1;
    }
    for (;;) {
        char buf[8];
        int readSize = read(fd, buf, sizeof(buf) - 1);
        if (readSize <= 0) {
            puts("quit");
            break;
        }
        buf[readSize] = 0;
        printf("readSize=%d, buf=`%s`\n", readSize, buf);
    }
    close(fd);
    return 0;
}

LD_PRELOADにslowio.soを指定して, readがゆっくりになってることを確認する.

test: slowio.so test_slowio.c
    gcc test_slowio.c -Wall -Wextra
    LD_PRELOAD=./slowio.so ./a.out
10月29日_
会社の個人予算でVAIO Duo 11 8GB + 256GB SSDをぽちっとしたけど納期が1カ月後らしい. うぅ….
10月28日_
喉が痛いのを我慢してたが, 頭痛も酷くなって限界なので病院に行った. かなりこじらせていたようでいつもは薬の少ない先生なのに6種類も出てしまった. はよ治そ.
10月27日_

初めてのkernel patch その4(patchの送り方)

その3でほぼ原因が分かった. bd_invalidated = 0はcheck_disk_size_change()が呼ばれるたびに個別にやるのじゃなくて, check_disk_size_change()の中でやれば一度ですむし忘れないと思う.

原因が分かったのでMLに投稿してみる. 結構いろいろお作法があって大変.

まずThe linux-kernel mailing list FAQを前半少しでも眺めて大体の感触をつかむ. kernelのgitをcloneしてコミットする. ftarceでも大変参考になった『LINUXカーネルHACKS』(大岩尚宏他)や初心者向けガイド: Linux カーネルのハックが参考になる.

patchメールのひな型をつくる. たとえば修正patchとリファクタリングpatchの二つを投稿したいならHEADから二つ前を指定して

git format-patch --cover-letter HEAD~2..HEAD -o outgoing -s

とする. --cover-letterをつけるとpatchメールだけでなく, 解説をつけるための先頭メールを作れる. -oでそのファイルの出力ディレクトリを指定する. -sは署名を付けるオプション. 文中にSigned-off-by: ...というのが挿入される. 他にもいろいろなオプションがあるようだ. kernelにはコードのスタイルや署名がついてるかなどを確認してくれるscripts/checkpatch.plというのがあるのでそれで警告されないか確認する(最重要).

次にMajordomo listsからどこに投稿するか決める. 修正箇所はfsになったけど, もともと問題が発生したのがmdadmだったから最初はlinux-raidに出してみたが華麗にスルーされた. まあ, これはメールの書き方もまずかったせいだと思うが.

実はkernelにはpatchを食わせるとどのMLやどの人に出せばよいか教えてくれるscript/get_mainteiner.plというものがある. 至れり尽くせりだ. MLの流量は物凄いのでほとんど流れてしまう. どこに出すかは大事な要素だ. 基本patchを見てほしい人にTo:やCc:で直接指定するスタイルなんだと思う. あと, MLは加入しなくても投稿できる.

さて, 本文を書いたらいよいよ投稿だ. git send-mailというコマンドがあるのでそれを使う. 私はniftyのメールサーバを使ったので.gitconfigに

[sendemail]
    smtpserver = smtp.nifty.com
    smtpuser = XXX
    smtpserverport = 587

と設定した. これらは--smtp-serverオプションなどでも指定できるので必須ではない.

git send-email outgoing/* --to=xxx --cc=xxx =smtp.nifty.com \
  --smtp-pass=XXX

とするとgitがメールを送ってくれる.

と書いてると何もかもしきたりに従ってないといけないのかとびくびくしてしまうが, 必ずしもそういうわけではないようだ. もちろんpatchのフォーマットは正しくないと駄目だろうが メールはgit send-emailを使わなくても大丈夫. 実際, 最終的に取り込まれたメールは手動で添付したものだったw

10月26日_

初めてのkernel patch その3

open("/dev/md/...") → md_open() → check_disk_change() → flush_disk() という流れかと思ったけどflush_disk()は呼ばれないのであった.

気を取り直してその1の__invalidate_device()への呼び出しの流れをもう一度みると __blkdev_get() → rescan_partitions() → invalidate_partition()となっている. block deviceがopenされたとき, まず汎用的なblkdev_get()が呼ばれ, __blkdev_get()の中で各deviceに応じたopenが呼ばれるようだ. そこで__blkdev_get()の中でopenやrescan_partitions()が呼ばれているあたりを眺めてみよう.

__blkdev_get():
  ...
  disk->fops->open(bdev, mode); // ここでmd_open()が呼ばれる
  ...
  // ここでrescan_partitions()が呼ばれる
  if (bdev->bd_invalidated) {
     if (!ret)
         rescan_partitions(disk, bdev);
     else if (ret == -ENOMEDIUM)
         invalidate_partitions(disk, bdev);
  }

このコードを見るとbdev->bd_invalidated = 1のときrescan_partitions()が走ることがわかる. deviceが変わったらbd_invalidated = 1になるのはいいけど, open()するたびにrescan_partitions()が呼ばれるのはbd_invalidatedフラグが立ったままなんだろう. だから次はbd_invalidatedが変更されている箇所を探す. 幸い多くない. 初期化を除いて明示的にクリアしているのは2箇所. block/partition-generic.cのinvalidate_partitions()とrescan_partitions().

invalidte_partitions():
   ...
   set_capacity(disk, 0);
   check_disk_size_change(disk, bdev);
   bdev->bd_invalidated = 0;
   ...

rescan_partitions():
   ...
   if (disk->fops->revalidate_disk)
       disk->fops->revalidate_disk(disk);
   check_disk_size_change(disk, bdev);
   bdev->bd_invalidated = 0;

どちらもcheck_disk_size_change()を呼んでからbd_invalidatedをクリアしている. check_disk_size_change()が呼ばれている箇所は3箇所. 上記2箇所を除いた残り1箇所はrevalidate_disk().

int revalidate_disk(struct gendisk *disk)
{
    struct block_device *bdev;
    int ret = 0;

    if (disk->fops->revalidate_disk)
        ret = disk->fops->revalidate_disk(disk);

    bdev = bdget_disk(disk, 0);
    if (!bdev)
        return ret;

    mutex_lock(&bdev->bd_mutex);
    check_disk_size_change(disk, bdev);
    // bdev->bd_invalidated = 0; // QQQ
    mutex_unlock(&bdev->bd_mutex);
    bdput(bdev);
    return ret;
}

もしかして, ここでbd_invalidatedのクリアを忘れているのでずっとbd_invalidtedが立ったままになってるんじゃなかろうか. 試しにQQQの部分をコメントアウトしてkernelをbuildしてテストする. するとopen()してもキャッシュがクリアされない!

10月25日_

初めてのkernel patch その2

てっきり-mm treeに入ったかと思ったけど, 物言いが入って判定待ち. 修正方法が正しくなかったのかな.

仮に修正方法が間違ってたとしても原因はそのへんにあると思うのでその1の続き. ftraceによりmdadmが悪さしてるようだとわかったのであった. mdadmはresizeコマンドを実行したあとdaemonとして動作しているようだ. mdadmを-g3 -O0でbuildしなおしてgdbの-p <pid>でattachする(ちなみに-gじゃなくて-g3だとマクロも見えるのでなにかと便利).

gdb) bt
#0  0x00007f85b1ebf103 in __select_nocancel ()
    at ../sysdeps/unix/syscall-template.S:82
#1  0x000000000040de66 in mdstat_wait (seconds=1000) at mdstat.c:317
#2  0x000000000042dea1 in Monitor (devlist=0x0, mailaddr=0x1a7b020 "root",

おお, 1000秒(=16分40秒)待ってる. ということはここを変えれば17分も待たなくてすむ. これでだいぶ調査しやすくなった.

この値は--delayオプションで変更できることもわかったがmonitorはresizeのときに勝手に呼ばれるので設定できない. monitorを先に起動しておけばよいのかもしれないがとりあえずソースをいじった.

# あとで一度killしてdaemonを起動すればよいとわかった(ただしもちろんresize中にkillは駄目だろう).

/sbin/mdadm --monitor --delay=30 --pid-file /var/run/mdadm/monitor.pid \
 --daemonise --scan --syslog

ちなみにmdadmのマニュアルにはデフォルトは60秒とある. しかしコードを見るとkernel 2.6.16以降は1000秒になってるようだ. あとで聞くとubuntu 10.04のときは60秒おきにキャッシュがflushされる現象があったらしい. 同じ原因やったのね.

// mdadm.c
if (delay == 0) {
    if (get_linux_version() > 2006016)
        /* mdstat responds to poll */
        delay = 1000;
    else
        delay = 60;
}

最初からmdadmを強く疑って1000でack(grep)しておけばすぐたどりつけたかもしれない. ただ, そのときはきっかり1000秒という確信はなかったので仕方ないか.

それはともかく上記スタックトレースにしたがってMonitor()の中のwhileループ→check_array()とキャッシュがクリアされる箇所を探す. このあたりはstep実行で1行進めてfreeコマンドでファイルキャッシュ容量を確認するということを繰り返した.

static int check_array(struct state *st, struct mdstat_ent *mdstat,
               int test, struct alert_info *ainfo,
               int increments, char *prefer)
{
    /* 変数宣言(snip) */

    if (test)
        alert("TestMessage", dev, NULL, ainfo);
    fd = open(dev, O_RDONLY);

すると関数に突入したすぐのopenでファイルキャッシュがflushされることがわかった. ここでdevはたとえば"/dev/md/test"を指す. O_RDONLYがついてるのにflushされるのはおかしい. 試しにddコマンドでデバイスを直接読んでみるとやっぱりflushされた.

sudo dd if=/dev/md/test of=/dev/null bs=4096 count=1

とするとkernel driverのmdが怪しい. mdに対するopenシステムコールだからmd_openの中だろうと思って今度はそちらを追いかけてみる. md_openはlinux/drivers/md/md.cにある. ちなみに同じディレクトリにはdm(device mapper)のコードも入ってて名前が紛らわしいので, 私はしょっちゅう間違える.

md_openの中で怪しそうなのはcheck_disk_change(). こいつはfs/block_dev.cにある.

int check_disk_change(struct block_device *bdev)
{
    struct gendisk *disk = bdev->bd_disk;
    const struct block_device_operations *bdops = disk->fops;
    unsigned int events;

    events = disk_clear_events(disk, DISK_EVENT_MEDIA_CHANGE |
                   DISK_EVENT_EJECT_REQUEST);
    if (!(events & DISK_EVENT_MEDIA_CHANGE))
        return 0;

    flush_disk(bdev, true);
    if (bdops->revalidate_disk)
        bdops->revalidate_disk(bdev->bd_disk);
    return 1;
}

おお, flush_disk()がある! ということはここかと一瞬思ったのだけど, その1で書いたように定期的にキャッシュがクリアされてるときはflush_disk()は呼ばれていなかったのだ. だとするとmd_open()ではなかった.

10月24日_

初めてのkernel patch その1

たった1行のLinux kernel patchだが, 初めて作ったやつが-mm treeに入れてもらえた(mm-commits→と思ったけどまだ? →取り込まれた). patchを作る経緯はなかなか面白かったので, そのあたりを書いてみよう.

なお, 私のLinuxの知識なんてはるか昔で止まってるし, 少し前からubuntuを使うようになってkernel buildすらご無沙汰なレベル. だから以下は, ベテランの人にとっては何を回りくどいことをという話の可能性が高い. そのときはよりよい方法をアドバイス頂けるとありがたい.

事の発端は, 本社のサービスで一部急に負荷が高くなって接続しにくくなることがあったこと. 調べるとどうやら約17分おきにファイルキャッシュがflushされていたせいらしい. で, その原因を調査することになった.

サービスでは複数のハードディスクをまとめて管理する機構(lvm:logical volume manager)の上に, ソフトウェアRAID(md:multiple device)を乗せて使っていた.

ディスクの容量が足りなくなるとlvmレベルで増やしてmdを管理するmdadmを使って拡張する. それらの操作を, その上に構築したファイルシステムをumountせずに(サービスを止めなくても)できるのがありがたい.

ところがそのmdadmで一度リサイズすると, その後定期的にファイルキャッシュがflushされてしまうのだ.

こういうのはまず手元の小さな環境で再現させるのが難しい. 環境によっては17分じゃないかもしれないし, 何がパラメータか分からないので1時間ぐらい待ったり, 設定ミスってやり直したりで若干手間取ったがなんとか再現できた.

再現できたので一安心(再現しないと調べようがない!). それでは原因の調査に入ろう.

まずリサイズは時間もかかるし, そのときにflushされることはあるかもしれないが, リサイズ後も定期的にflushされるというのはmdadmとは違うところで起こってるのかなと想像する.

実際, mdadmのソースを見てもファイルキャッシュをflushさせるioctlを発行してるようには見えなかった. てか, 普通わざわざそんなことしないだろう.

で, 誰が一体やってるのだろうとkernelのrevalidate_disk()やflush_disk()などを呼び出してるやつを探すことにした. やり方はftaceを使ってLinux kernelのある関数が呼ばれたときのstack traceを知る方法で書いた.

それらの関数はmdadmでstopやcreateなどを実行したときに呼び出されてるので, flushされるときも呼ばれてるだろうと. ところが17分待っても呼ばれない. それなのにファイルキャッシュはflushされている! ということはこいつらじゃない.

毎回17分待つのが辛いのだが, 試行錯誤してやっと__invlidate_device()が呼ばれていることがわかった.

mdadm-900 [001] .... 75491.384221: __invalidate_device <-invalidate_partition
mdadm-900 [001] .... 75491.384235: <stack trace>
=> ftrace_call
=> invalidate_partition
=> rescan_partitions
=> __blkdev_get
=> blkdev_get
=> blkdev_open
=> do_dentry_open
=> nameidata_to_filp
=> do_last
=> path_openat
...

このlogの先頭を見ると__invlidate_device()を呼び出した犯人がmdadmであること(mdadm-900:900はPID)がわかる. あれ? 犯人はやっぱりあんただったのか(続く).

10月23日_
熱が腰にいったのかな, とてもだるい. 今日はウルトラワークにしといてよかった. 満員電車に乗ってられない.
10月22日_
風邪引いて胃のあたりがむかむかする.
10月21日_
朝ランニングしてたら道を間違えた. あと1kmぐらいで家に着くと思っていたら, 3kmも離れたところを逆走していた. なんでそんなに離れてるのに気がつかなかったのかよく分からない.
10月19日_
誤認逮捕された大学生は大学を退学されているようだが, ああいうのは国や県が何かフォローするのだろうか. もちろん長期間の裁判の上, 冤罪だったというケースですら微々たる補償しかなされないようだが(失った時間は戻せないし), 今回のはすぐ分かったんだから何らかのサポートがあってほしいな.
追記:10/23にみたクローズアップ現代の特集では「大学生」となっていたので退学にはならなかったのか復学できたのか
10月17日_

ftaceを使ってLinux kernelのある関数が呼ばれたときのstack traceを知る方法

ubuntu 12.04では最初からこの機能が使えるようにkernelがbuildされていた. たとえば__invalidate_device()がいつどのように呼ばれたか知りたければ次のようにする.

mount -t debugfs debugfs /sys/kernel/debug
echo 1 > /proc/sys/kernel/stack_tracer_enabled
cd /sys/kernel/debug/tracing
echo __invalidate_device > set_ftrace_filter
echo function > current_tracer
echo 1 > options/func_stack_trace
echo 0 > trace
cat trace_pipe

デフォルトはmountされてないので一度mountをする. stack_tracerを有効にし, set_ftrace_filterに__invalidate_deviceを設定する. current_tracerをfunctionにしてtraceを開始し, optionでstack traceも出るようにする. 最後に今までのlogをクリアしてtraceの中身を表示する.

これでたとえばこんな風に表示される. 『LINUXカーネルHACKS』(大岩尚宏他)が大変参考になった.

mdadm-900 [001] .... 75491.384221: __invalidate_device <-invalidate_partition
mdadm-900 [001] .... 75491.384235: <stack trace>
=> ftrace_call
=> invalidate_partition
=> rescan_partitions
=> __blkdev_get
=> blkdev_get
=> blkdev_open
=> do_dentry_open
=> nameidata_to_filp
=> do_last
=> path_openat
=> do_filp_open
=> do_sys_open
=> sys_open
=> system_call_fastpath
10月16日_

『南極点のピアピア動画』(野尻抱介)

タイトルも解説もふざけてるけど中身はまっとう. 展開が速すぎるのは『ふわふわの泉』の1章みたいなもんで, もったいない, ゆっくり読みたいと思ってしまう. 「ていうかゾラックだな」はニヤリとするしかないだろう. この能天気さ, UGC, CGM万能感は科学万博が盛んだったころの科学万能感に似ている.
校正すらソーシャル(「南極点のピアピア動画」のソーシャル校正 ).

初音ミクやニコニコ動画はほとんど見たことないけど雰囲気は十分伝わってくる. 今見たら著者はこんなこと(あーやさんの髪を検証してみた)をしてるのか. 確かに解説で「野尻さんてSF作家だったのか」とか「野尻仕事しろ」って書かれてもしかたない気が.

「古い」と断ってるとはいえ流石に「この時代」にWEPはないんちゃうかと思った.
10月15日_

『ゲイルズバーグの春を愛す』(ジャック・フィニィ 福島正実訳)

@shuyoさんにお借りしたSFの古典. 訳がいい. タイトルのつけかたもかっこいい. 英語をカタカナにしただけの洋画の題をつける人, 見倣えみたいな. 表題作と気球に乗る話(『少年少女飛行倶楽部』(加納朋子)を連想)と過去との文通の話(少し前に読んだ本に同様のモチーフが…なんだっけ)が好み.

10月11日_
ジュンク堂でトークセッションお役目終了. 他の方のおかげでなんとか終わってほっとする. 参加してくださった方, どうもありがとうございました.
10月10日_

今更誰得な情報だけどVisual StudioのコンソールアプリをIDEで実行したとき終了時にコンソールを閉じない方法

私はWindowsでコンソールアプリを作るときVCのIDEでCtrl-F5(デバッグ無しで開始)をよく使うのだけどいつのころからか(VS2010ぐらい?), アプリが終了するとコンソールが閉じられてしまって困っていた.

ところが先日何かの拍子にVS2012で閉じないプロジェクトができてしまった. 何が違うのかプロパティを一つ一つ確認して違いはリンク時のサブシステムオプションということが分かった. ここを/SUBSYSTEM:CONSOLEにしておくと終了時にコンソールが残ったままになる.

確かAdvanced Windowsにここは設定無しにしておくとコンソールアプリとGUIアプリどちらでもOKになると書いてあって, それ以来ずっと設定無しにしてたけど, そういう違いがあったんだなあ. ちょっとすっとした.

10月8日_

swigを使ってC++のライブラリをJavaから呼び出す.

昨日の続き. str.hppという簡単なクラスを作ってみる.

// str.hpp
#pragma once
#include <string>

struct Data {
    int x_;
    std::string str_;
    Data()
        : x_(5)
        , str_("hello")
    {
    }
    void set(int x, const std::string& str)
    {
        x_ = x;
        str_ = str;
    }
    int getInt() const { return x_; }
    const std::string& getStr() const { return str_; }
};

str.iを用意する.

%module Str
%include "std_string.i"

%{
#include "str.hpp"
%}

%include "str.hpp"

std::stringは専用のラッパーが用意されていてstd_string.iというのを%includeするとよい. %{ %}でくくられたところはwrapファイルにそのまま出力される. そのあとに書かれたものがラップされる. %includeすればstr.hppがそのまま使われる. str.iをコンパイルするとStr.javaとStrJNI.javaができる.

swig -java -c++ -Wall str.i
javac Str.java StrJNI.java
cl /LD str_wrap.cxx -I%JAVA_INCLUDE% \
  -I%JAVA_INCLUDE%\win32 str.lib /W3 /EHsc

str_wrapをloadLibraryするとDataクラスが使えるようになる. 正しく動作している. すばらしい.

public class Test2 {
    static {
        System.loadLibrary("str_wrap");
    }
    public static void main(String argv[]) {
        Data data = new Data();
        System.out.println("x=" + data.getInt());
        System.out.println("str=" + data.getStr());

        data.set(123, "abcdefg");
        System.out.println("x=" + data.getInt());
        System.out.println("str=" + data.getStr());
    }
}
>x=5
>str=hello
>x=123
>str=abcdefg

デストラクタを追加してみる.

~Data()
{
    puts("dstr");
}

最初, あれデストラクタが呼ばれないと思ったけれど, Test2.java側で明示的にgcさせれば呼ばれていることを確認できる.

Data data = new Data();
/* この2行を追加すると"dstr"が表示された. */
data = null;
System.gc();

次にgettterを正しくとれたかを示すbool値を返すようにし, 値はポインタ経由で取得するようにしてみる.

bool getRet(int *OUTPUT) const
{
    *OUTPUT = x_;
    return true;
}

Javaにはポインタはないのでサイズ1のint配列を渡すことになる. またstr.iに付加情報を追加する必要がある.

// str.i
%module Str
%include "std_string.i"

%{
#include "str.hpp"
%}

%include typemaps.i // 追加部分
%include "str.hpp"

// Test2.java
int[] ret = {0};
boolean b = data.getRet(ret);
System.out.println("b=" + b + ", ret=" + ret[0]);

str.hppのOUTPUTという変数名はこれでないとエラーになった. int以外のポインタ型を混在させたいときでも動くのかな. あとで確認する.

[追記] Lib\java\typemaps.iを見ると整数型や浮動小数点数だけサポートしているようだ.

あるいはJava側でintのポインタ型を表すSWIGTYPE_p_intを用意してそれを使う手もある. ただ煩雑すぎる気がする(詳しくはswigのExamples/java/pointer参照).

例外も扱える(ちゃんとドキュメントを以下略). ただstd::exceptionの継承はできないようだ. またthrow ()に正しく例外クラスを指定しないと正常動作しない. VCでは無視されるけどswigでは必須.

// str.hpp
struct MyException {
};
bool getRet(int *OUTPUT) const throw (MyException)
{
    *OUTPUT = x_;
    throw MyException();
    return true;
}

Java側ではRuntimeExceptionで受けることになった. ここらへん変更可能かは要調査.

// Test2.java
} catch (RuntimeException e) {
    System.out.println("err=" + e);
}

str_wrap.cxxを見る限りではMyExceptionはつぶされていたので工夫はいりそう. とりあえずSWIG_JavaThrowExceptionの文字列メッセージを渡しているところを_e.what()に置き換えればJava側にそれが伝わった.

  // getRet()のラッパーの一部
  // _eを受けてもそれを無視して上位側に伝えている
  catch(MyException &_e) {
    (void)_e;
    SWIG_JavaThrowException(jenv, SWIG_JavaRuntimeException
      , "C++ MyException exception thrown");
    return 0;
  }

まとめると整数やstd::stringのやりとりは比較的容易にできる. ただしエラーを伝えたい場合, 引数にエラーを通知する変数をポインタを渡すか, 例外と飛ばすか, どちらも微妙に面倒になるので悩ましいところ.

[追記] swigのLib\javaを見るのが一番いいと分かった. ドキュメントには書かれてない有用な情報がいろいろ分かる. std_except.iを%includeすると標準の例外を扱える. これが一番簡単そうだ.

// str.hpp
bool getRet(int *OUTPUT) const throw (std::runtime_error)
{
    throw std::runtime_error("getRet:negative");
}
// str.i
%include "std_except.i"
を追加.
// Test2.Java
} catch (RuntimeException e) {
    System.out.println("err=" + e); // これでC++で指定した文字列を受け取れる
}

std_except.iには

%typemap(throws) std::runtime_error     "SWIG_JavaThrowException(jenv, SWIG_JavaRuntimeException, $1.what());\n return $null;"

などと書かれているところを見ると自前の例外クラスを処理するのも難しくなさそう.

10月7日_

swigを使ってCのライブラリをJavaから呼び出す.

かなり昔, swigを使ってみたことがあったのだけど64bit Windows環境でもちゃんとできるのか確認してみた. まずJDK(jdk1.7.0_07)とswig-2.0.8をインストール. version文字列を表示するversion.hとversion.cを用意する.

// version.h
#pragma once
const char *getVersion();
// version.c
#include "version.h"
const char *getVersion()
{
    return "1.2.345";
}

これらからライブラリを作る.

>cl /c version.c
>lib version.obj

>cl test.c version.lib

version.libができたのでCで正しく動くか一応確認.

// test.c
#include "version.h"
#include <stdio.h>

int main()
{
    printf("version=%s\n", getVersion());
    return 0;
}
//
>cl test.c version.lib
>test
>version=1.2.345

問題ない. 次にswigの設定ファイルを用意する.

// version.i
%module Version
%{
#include "version.h"
%}
char *getVersion();

これをswigにかけるとVersion.java, VersionJNI.java, version_wrap.cができる. version_wrap.cからDLLを作る.

/* JAVA_INCLUDEはJDKのパスを設定する. */
>swig -java version.i
>cl /LD version_wrap.c -I%JAVA_INCLUDE% -I%JAVA_INCLUDE%\win32 version.lib

version_wrap.dllを使うTest.javaを用意する.

public class Test {
    static {
        System.loadLibrary("version_wrap");
    }
    public static void main(String argv[]) {
        System.out.println("version=" + Version.getVersion());
    }
}
>javac Version.java VersionJNI.java
>javac Text.java
>java Test
version=1.2.345

無事呼び出せた(実はここにくるまで結構はまったのだが). ただversion.libじゃなくてversion.dllを作るとうまくいかなかった. loadLibraryまではいくけどVersion.getVersion()のところで例外発生. 原因は調べてない. それはとりあえず置いといてC++ではstd::stringやexceptionも使えるはずなので次はそれを確認しよう.

10月4日_

PRML の読む章・飛ばす章(私家版)

ジュンク堂でトークセッションなんて現実離れしていて実感を伴ってないのだが, takesakoさんのはかりごとはからいにより私も末席を努めることになっている. お時間とご興味のある方はぜひご参加を.

暗黒通信団の黒幕なんて聞くと真っ黒すぎて実在してるのか分からないのだけど, こういう集まりがあるということはやっぱり存在してるのだろう. ダークマターみたいだ.

10月2日_
どんな感じかと第3回 W8 PRML読書会に参加.
演習問題を解くところから始まる. 時間がかかるのはしかたないか. ただPRMLは1行も飛ばさないで読むべし, という本でもないのでさじ加減が難しい. 後半疲れてくるとやや自制が効かなくなってしゃしゃりでてしまった. すいません. 毎回は無理だけどまた参加してみたいな.
10月1日_

先日の『生き抜くための数学入門』(新井紀子), 論理的に考えることの重要性の紹介のところで

「…とは何か」という議論は, 国際社会の中で外国とつきあうには, いやというほどやらなければならない

と書かれている. 自分勝手な主張は誰も耳を傾けない.

小さな国の提案であっても, 内容が論理的かつ合理的であれば, 他の国も耳を傾けざるを得ません.

別の利害をもった人たちがあつまったとき, 最初にしなければならないのが「とは」を整理すること,

「この定義からはじめましょう」と決めるだけで, じつに多くの争いを回避することができる

今までいろんな数学の本を見たけど, 定義や論理の大切さをここまでわかりやすく書いたものって記憶にない.

強いリーダーなんていらない. 一任するな. 自分で考えろ.

...台風一過で見渡す限り雲がなかった. きれいさっぱり.


御意見は光成滋生<herumi@nifty.com>までお願いします