c-wrapperを使う

c-wrapperを使うと、C言語で書かれたライブラリ関数をGaucheから直接呼び出すことができる。
http://homepage.mac.com/naoki.koguro/prog/c-wrapper/index-j.html
ということなので使ってみる。マニュアルは、配布されているアーカイブファイルに一緒に入っている。

c-loadと、c-load-library + c-includeとの違い?

まず、わりとどうでも良いことから。
リファレンスマニュアルにはc-loadは、c-load-libraryとc-includeを一度におこなうもとだという説明がある。そうすると、

(use c-wrapper)
(c-load-library "libc")
(c-include "stdio.h" :compiled-lib "libfoo")

と書いても

(use c-wrapper)
(c-load "stdio.h" :libs "-lc" :compiled-lib "libfoo")

と書いてもどちらでも良い。でもc-load-libraryを使うとなぜかcwcompileを通らない(環境の問題?)。cwcompileを使わないぶんにはどちらでも良さそうだけど。

stdio.hで宣言されている関数を使ってみる

freadで入力を読み込んでfwriteで出力するプログラムを書く。

size_t fread(void* ptr, size_t size, size_t n, FILE* fp);
size_t fwrite(const void* ptr, size_t size, size_t n, FILE* fp);

freadやfwriteを使うにはchar型の配列を用意しないといけないが、領域の確保にはmakeを使う。たとえば

(make (c-array <c-uchar> n))

としてやると、大きさnのunsigned char配列(に対応するオブジェクト)が得られる。
標準入力・標準出力を表す定数stdin、stdoutはstdio.hで定義されているのでGaucheのプログラム内でもそのまま使えるようになる。

#!/usr/bin/env gosh
(use c-wrapper)
(c-load "stdio.h" :libs "-lc")

(define (main arg)
  (define buffer-size 10)
  (define buffer (make (c-array <c-uchar> buffer-size)))

  (let1 i (fread buffer (c-sizeof <c-uchar>) buffer-size stdin)
    (fwrite buffer (c-sizeof <c-uchar>) i stdout)))

ポインタとキャストを使う

関数へのポインタを受け取るC関数に、Gaucheの手続きを渡すこともできる。たとえばqsort

void qsort(void *base, size_t n, size_t size, int(*compare)(const void *, const void *));

に渡す比較関数とか。例えば次のようなプログラムでソートをおこなうことができる。

#!/usr/bin/env gosh
(use c-wrapper)
(c-load "stdlib.h" :libs "-lc")

(let* ((xs         '(1 200000000 3 400000 50 60000 70 8 90000000 10))
       (array-size (length xs))
       (array      (cast (c-array <c-int> array-size) xs)))
  (qsort array array-size (c-sizeof <c-int>)
    (lambda (x y) 
      (let ((x-value (ref (deref (cast (ptr <c-int>) x))))
            (y-value (ref (deref (cast (ptr <c-int>) y)))))
        (- x-value y-value))))
  (print (cast <list> array)))

このプログラムの

(ref (deref (cast (ptr <c-int>) x)))

の部分の説明を書いておくと

  1. 比較関数が受け取る引数はvoid*型ポインタ。Gauche内では、型。
  2. int型へのポインタに変換する。(cast (ptr ) ...)。型。
  3. ポインタの指しているデータを得る。(deref ...)。型。
  4. Gaucheの数値を得る。(ref ...)。型。

ただ、ポインタとキャストの挙動・扱い方がまだよくわかっていないから、何か変かもしれない。関数へのポインタの扱いもよくわからなくて、たとえば

(cast <c-func-ptr> (lambda ...))

と書いて良さそうな気がするのだけどエラーが出る。

Cの文字列とGaucheの文字列の扱い

Gaucheの文字列をそのままCの関数に渡すと、Cの文字列に変換してくれるみたい。

(printf "hello, world\n")

次のようにしても動く。

(printf (cast (ptr <c-char>) "hello, world\n"))

一方、Cの文字列(配列)はそのままではGaucheの文字列として扱うことはできない。
変数bufferにCの文字列が入っている場合、

(cast <string> (ptr buffer))

とすることでGaucheの文字列が得られる(ptrはポインタを取得する手続き)。

(cast <string> buffer)

ではエラーになった。

仮想ポートに使ってみる

完全仮想ポート 、仮想バッファポート のオブジェクトを、freadとfwriteを使って作ってみた。

#!/usr/bin/env gosh
(use gauche.vport)
(use gauche.uvector)
(use c-wrapper)
(c-load "stdio.h" :libs "-lc")

(define v-in 
  (make <virtual-input-port>
    :getb
    (lambda ()
      (let* ((c (make <c-uchar>))
             (i (fread (ptr c) (c-sizeof <c-uchar>) 1 stdin)))
        (if (= i 0)
            (eof-object)
            (cast <integer> c))))))

(define v-out
  (make <virtual-output-port>
    :putb
    (lambda (b)
      (fwrite (ptr (cast <c-uchar> b)) (c-sizeof <c-uchar>) 1 stdout))))

(define b-in
  (make <buffered-input-port>
    :fill
    (lambda (u8vec)
      (let* ((buf-size (u8vector-length u8vec)) 
             (buffer (make (c-array <c-uchar> buf-size)))
             (read-size (fread buffer (c-sizeof <c-uchar>) buf-size stdin))
             (u8buffer (cast <u8vector> buffer)))
        (u8vector-copy! u8vec 0 u8buffer 0 read-size)
        read-size))))

(define b-out
  (make <buffered-output-port>
    :flush
    (lambda (u8vec flag)
      (fwrite (cast (ptr <c-uchar>) u8vec) (c-sizeof <c-uchar>)
        (u8vector-length u8vec) stdout))))

(define (main args)
  (display "virtual-port\nin: ")
  (display #`"out: ,(read-line v-in)\n" v-out)
  (display "buffered-port\nin: ")
  (display #`"out: ,(read-line b-in)\n" b-out))