諸事情 で Python の ctypes のコードを Ruby の fiddle に移植しているんですが、どちらもだいたい同じことができる、一方APIなどの設計思想が違う、みたいな感じで興味深いです(興味深い)。
今回ctypesで普通にやるような、「参照先を更新する関数」の呼び出しがfiddleの場合ちょっと癖があったのでメモ。
C側
#define INT_VALUE 1234567 int update_int(int* ptr) { *ptr = INT_VALUE; return 0; }
これを shared ライブラリにする。
gcc -fPIC -c sample.c -o sample.o gcc -shared sample.o -o libsample.so
ctypesでは
from ctypes import * lib = CDLL("./libsample.so") i = c_int() print(lib.update_int(byref(i))) print(i.value)
python3 ct.py 0 1234567
byref
が癖ですが結構普通に扱える感じです。
fiddleでは
コード全体です。
require 'fiddle/import' module CLib extend Fiddle::Importer dlload "./libsample.so" extern "int update_int(*int)" end i = Fiddle::Pointer.malloc(Fiddle::SIZEOF_INT) p CLib.update_int(i) p i[0, i.size].unpack("i!")
ruby fiddle.rb 0 [1234567]
詳細
まず、 fiddle/import
というライブラリを使い、特定のRuby側のモジュールとCのライブラリを紐付けます。 extend Fiddle::Importer; dlload "./libsample.so"
の箇所ですね。
extern
というメソッドで関数を取り出して使えるようになります。シンボルと同名のメソッドを定義します。この辺はるりまの通り。
で、 int
を格納するための領域を作らないといけないんですけど、 Ruby の fiddle の場合各型ごとの専用のコンストラクタはなくて、 Fiddle::Pointer.malloc
で汎用的に確保します。型ごとの(ビルドした環境での)サイズは定数があります。
あとはその Fiddle::Pointer
インスタンスを引数に渡せば、参照先が更新されます。
されるんですが、 fiddle の場合はその値を取り出すのにも癖があって、メモリ上の表現を String#unpack
で変換してあげる必要があります。
以下のように size
分のcharを取り出し、型が int なら例えば i!
を指定します。
i[0, i.size].unpack("i!")
long, double などの場合も同様にできて、詳細は以下のテンプレート文字列の説明をご覧ください。本来エンディアンを考慮すべきですが、そこ含めて String#unpack
がやってくれます。
最後に
基本的な型について上記の操作を確認する実装を以下のgistに置いておくので、ご参考まで。
なお
ffi.gem も存在します(上にfiddleより人気みたい...)が、今回は標準添付のライブラリfiddleを使いました。
各種バージョン情報です。
vagrant@ubuntu-bionic:~$ ruby --version ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux-gnu] vagrant@ubuntu-bionic:~$ python3 --version Python 3.6.9 vagrant@ubuntu-bionic:~$ uname -a Linux ubuntu-bionic 5.0.0-29-generic #31~18.04.1-Ubuntu SMP Thu Sep 12 18:29:21 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux vagrant@ubuntu-bionic:~$ gcc -v Using built-in specs. COLLECT_GCC=gcc COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/7/lto-wrapper OFFLOAD_TARGET_NAMES=nvptx-none OFFLOAD_TARGET_DEFAULT=1 Target: x86_64-linux-gnu Configured with: ../src/configure -v --with-pkgversion='Ubuntu 7.4.0-1ubuntu1~18.04.1' --with-bugurl=file:///usr/share/doc/gcc-7/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++ --prefix=/usr --with-gcc-major-version-only --program-suffix=-7 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu Thread model: posix gcc version 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1)