拡張ライブラリチュートリアルをやってみる#3
普通に C で書いた処理をうまく繋げられるのが,いい感じですね.
配列を操作する
配列内の数値をすべて掛け合わせるような処理を実装する.
処理実体
配列とその長さを受け取って,配列内の値を順に掛け算していく.
- mul_all.c
float mul_all(array, nx) float array[]; int nx; { float result = 1.0; int i; for(i=0; i<nx; i++){ result = result * array[i]; } return(result); }
これを検証するためのコード.
- main.c
#include <stdio.h> float mul_all(float array[], int nx); int main() { int n = 5; float a[n]; int i; for(i=0; i<n; i++){ a[i] = i + 1; printf("%f\n", a[i]); } printf("mul_all()=%f\n", mul_all(a, n)); }
% cc -o mul_all mul_all.c main.c % ./mul_all 1.000000 2.000000 3.000000 4.000000 5.000000 mul_all()=120.000000
C と Ruby の接続
そしてラッパーです.今回は NArray を使うそうなので,そのインストール.
% sudo gem install narray
ラッパーの記述
- test3.c
#include "ruby.h" #include "narray.h" float mul_all(float array[], int nx); VALUE wrap_mul_all(self, na) VALUE self, na; { VALUE na2; struct NARRAY *n_na; float result; na2 = na_cast_object(na, NA_SFLOAT); GetNArray(na2,n_na); result = mul_all((float*)n_na->ptr, n_na->total); return( rb_float_new(result) ); } void Init_test() { VALUE module; rb_require("narray"); module = rb_define_module("Test"); rb_define_module_function(module, "mul_all", wrap_mul_all, 1); }
wrap_mul_all の流れとしては,
- Ruby からやってきた VALUE 型構造体(na)を,NArray オブジェクト(na2)へ cast する (na_cast_object)
- それ(na)が NArray オブジェクトであれば,データ実体(NARRAY)へのポインタ(n_na)を返す(GetNArray)
- mul_all は float 型の配列を受け取り((float*)n_na->ptr),計算する
- 計算結果を Ruby の FLOAT 型に変換して返す (rb_float_new)
な感じかな?もうちょい C を勉強し直さないとダメだなorz.
コンパイルと実行
今回は外部ファイルが必要なので,その記述も含めて extconf.rb を書きます.
- extconf.rb
require 'mkmf' dir_config('narray', $sitearcchdir, $sitearcchdir) if !(have_header('narray.h') and have_header('narray_config.h')) print <<-EOS ** configure error ** Header narray.h or narray_config.h is not found. If you have these files in /narraydir/include, try the following: % ruby extconf.rb --with-narray-include=/narraydir/include EOS exit -1 end if /cygwin|mingw/ =~ RUBY_PLATFORM unless have_library('narray') print <<-EOS ** configure error ** libnarray.a is not found. % ruby extconf.rb --with-narray-lib=/narraydir/lib EOS exit -1 end end create_makefile('test')
そしてコンパイルと実行
% ruby extconf.rb --with-narray-include=/var/lib/gems/1.8/gems/narray-0.5.9.6 % make % irb >> require 'narray' => true >> require 'test' => true >> a = NArray.sfloat(5).indgen!(1,1) => NArraysfloat5: [ 1.0, 2.0, 3.0, 4.0, 5.0 ] >> Test.mul_all(a) => 120.0
NArray へのメソッド追加
次は Ruby らしく,NArray#mul_all として追加してみることに.
- test4.c
#include "ruby.h" #include "narray.h" float mul_all(float array[], int nx); VALUE wrap_mul_all(self) VALUE self; { VALUE na2; struct NARRAY *n_na; float result; na2 = na_cast_object(self, NA_SFLOAT); GetNArray(na2, n_na); result = mul_all((float*)n_na->ptr, n_na->total); return( rb_float_new(result) ); } void Init_test() { VALUE class_na; rb_require("narray"); class_na = rb_const_get(rb_cObject, rb_intern("NArray")); rb_define_method(class_na, "mul_all", wrap_mul_all, 0); }
基本的にさっきと同じようだが,
- メソッドが実行されている VALUE 型構造体(self)を,NArray オブジェクト(na2)へ cast する (na_cast_object)
と
- NArray クラスを取得する (rb_const_get)
- NArray クラス(class_na) に mul_all メソッドを追加する (rb_define_method)
と言う点が異なる.
そしてコンパイルと実行.
% mv test3.c test3.c.org % ruby extconf.rb --with-narray-include=/var/lib/gems/1.8/gems/narray-0.5.9.6 % make % irb >> require 'narray' => true >> require 'test' => true >> NArray.sfloat(5).indgen!(1,1).mul_all => 120.0 >>
何となく流れはわかったんだけど,もっと C の知識が必要だなーと,ちょっと凹んだ・・・・