残すはFFI::MemoryPointerの話のみ。
OUT引数
Cのインタフェースを設計するときに、関数にポインタを渡し、ポインタを介することで関数の出力を受け取るようにするということがよくある。
例えば、次のようなコードが一例。
/* rbuf.h */ typedef struct _ring_buffer rbuf_t; typedef unsigned int uint_t; void rbuf_create( rbuf_t** rbuf, // [OUT] created ring buffer uint_t length); // [IN ] buffer length uint_t // size of wrote rbuf_write( rbuf_t* rbuf, // [IN ] ring buffer const char* data, // [IN ] data to write uint_t length); // [IN ] data length uint_t // size of read rbuf_read( rbuf_t* rbuf, // [IN ] ring buffer char* buffer, // [OUT] buffer to read uint_t length); // [IN ] buffer length void rbuf_delete( rbuf_t* rbuf); // [IN ] ring buffer to delete
こういったインタフェースのときに、OUT引数には、アプリケーション側でメモリを用意して、そのポインタを渡す必要が出てくる。
例えば、Cだと次のような感じ。
#include <stdio.h> #include <string.h> #include "rbuf.h" int main() { /* OUT引数用にメモリを用意 */ rbuf_t* rbuf = NULL; char buffer[256]; uint_t wrote = 0u; uint_t read = 0u; /* ring bufferを作成 */ rbuf_create(&rbuf, 10u); /* データを書き込む */ wrote = rbuf_write(rbuf, "0123456789abcdef", 16); printf("wrote: %d\n", wrote); //=> wrote: 9 /* データを読み込む */ memset(buffer, 0, 256); read = rbuf_read(rbuf, buffer, 256); printf("read: %s (size %d)\n", buffer, read); //=> read: 012345678 (size 9) /* ring bufferを削除 */ rbuf_delete(rbuf); return 0; }
けど、そのインタフェースをRubyから使おうとすると、ちょっと困ったことになる。
というのも、Rubyでネイティブなメモリを用意し、そのアドレスをライブラリに渡すなんてことは出来ないから。
これを解決する一つの方法としては、標準ライブラリのmalloc()を結びつけて使用する方法がある。
malloc()を使えば、ネイティブなメモリが確保されてそのポインタが返ってくるので、そのポインタを引数として渡せばいい。
けど、そんな面倒なことをしなくても大丈夫。 Ruby-FFIにはFFI::MemoryPointerという便利なクラスがちゃんと用意されている。
FFI::MemoryPointer
FFI::MemoryPointerは、オブジェクトを生成すると、ネイティブなメモリを確保し、そのポインタを(ラップして)返してくれる。
# malloc(sizeof(型));と同等 pointer = FFI::MemoryPointer.new(型) # malloc(sizeof(型)*サイズ);と同等 pointer = FFI::MemoryPointer.new(型, サイズ)
Rubyの文字列からFFI::MemoryPointerのオブジェクトを生成することも出来る。
この場合、文字列のデータはネイティブなメモリ上にコピーされる。
# pointer = malloc(文字列のサイズ+1); # memcpy(pointer, 文字列, 文字列のサイズ+1);と同等 pointer = FFI::MemoryPointer.from_string(文字列)
これを使うと、先ほどのCのコードは、Rubyだと次のようになる。
module MyLib extend FFI::Library ffi_lib 'rbuf' class RBuf < FFI::Struct # Rubyからアクセスすることがなくても、 # ダミーでレイアウトを書いておかないとエラーになるっぽい layout(:dummy, :int) end attach_function :rbuf_create, [:pointer, :uint], :void attach_function :rbuf_write, [RBuf.ptr, :string, :uint], :uint attach_function :rbuf_read, [RBuf.ptr, :pointer, :uint], :uint attach_function :rbuf_delete, [RBuf.ptr], :void end # rbuf_t* rbuf = NULL; # char buffer[256]; rbuf_handle = FFI::MemoryPointer.new :pointer buffer = FFI::MemoryPointer.new :char, 256 # rbuf_create(&rbuf, 10u); MyLib.rbuf_create(rbuf_handle, 10); rbuf_pointer = rbuf_handle.read_pointer rbuf = MyLib::RBuf.new rbuf_pointer # wrote = rbuf_write(rbuf, "0123456789abcdef", 16); # printf("wrote: %d\n", wrote); //=> wrote: 9 wrote = MyLib.rbuf_write(rbuf, "0123456789abcdef", 16) puts "wrote: #{wrote}" # memset(buffer, 0, 256); # read = rbuf_read(rbuf, buffer, 256); # printf("read: %s (size %d)\n", buffer, read); //=> read: 012345678 (size 9) read = MyLib.rbuf_read(rbuf, buffer, 256) str = buffer.read_string(read) puts "read: #{str} (size #{read})" # rbuf_delete(rbuf); MyLib.rbuf_delete(rbuf);
rbuf_t**
の扱いがちょっと分かりにくいかもしれないけど、逐一説明すると、
rbuf_handle = FFI::MemoryPointer.new :pointer
ネイティブメモリ上に、「ポインタを納めるメモリ」を確保し、そのメモリのポインタ(つまり、ポインタのポインターーハンドル)を得る。MyLib.rbuf_create(rbuf_handle, 10)
ハンドルをrbuf_create()に渡すことで、そのハンドルの指し示す先(ポインタを納めるメモリ)に「作られたRBufオブジェクトのポインタ」を納めさせる。rbuf_pointer = rbuf_handle.read_pointer
ハンドルの指し示す先(ポインタを納めるメモリ)のデータを(ポインタとして)読むことで、そこに納められた「作られたRBufオブジェクトのポインタ」を得る。rbuf = MyLib::RBuf.new rbuf_pointer
「作られたRBufオブジェクトのポインタ」をキャストして、RBufオブジェクトを得る。
という感じ。
FFI::MemoryPointerのメソッド
オブジェクトを作成することでネイティブなメモリを確保できるけれど、当然そのメモリに読み書きが出来ないと使い物にはならないので、そのためのメソッドが用意されている。
(なお、これらはFFI::MemoryPointerの親であるFFI::Pointer(や、さらにその親であるFFI::AbstractMemory)で定義されているので、FFI::Pointerでも使用可能)
よく使いそうなものとしては、以下。
メソッド | 説明 |
---|---|
clear | メモリをゼロクリアする |
read_int | ポインタの位置の整数値を読み込む |
write_int(value) | ポインタの位置に整数値valueを書き込む |
read_float | ポインタの位置の浮動小数点数を読み込む |
write_float(value) | ポインタの位置に浮動小数点数valueを書き込む |
read_double | ポインタの位置の倍精度浮動小数点数を読み込む |
write_double(value) | ポインタの位置に倍精度浮動小数点数valueを書き込む |
read_pointer | ポインタの位置のポインタの値を読み込む |
write_pointer(value) | ポインタの位置にポインタの値valueを書き込む |
read_bytes(length) | ポインタの位置から長さlengthバイトを読み込み、バイト列として返す |
write_bytes(str, index=0, length=nil) | ポインタの位置にバイト列strを書き込む(indexやlengthを指定すると、部分バイト列を書き込む) |
read_string(length=nil) | ポインタの位置から文字列を読み込む(lengthを指定すると、その長さ分だけ読み込む) |
write_string(str, length=nil) | ポインタの位置に文字列strを書き込む(lengthを指定すると、その長さ分だけ書き込む) |
なお、intのところでは、shortやuint、int64といった型も利用できる。
また、オフセットを指定できる put_type、get_type(typeはintやbytesなど)もある。
他のメソッドや詳細は、MemoryPointerのrdocを参照。
オートリリース
FFI::MemoryPointerでネイティブな領域に確保されたメモリは、FFI::MemoryPointerのオブジェクトがGCで破壊されるタイミングで、自動的で解放される。
これと同様のことを構造体でやりたい場合、FFI::StructのかわりにFFI::ManagedStructを継承するようにする。
そうすると、オブジェクトが破壊されるタイミングでreleaseクラスメソッドが呼び出されるので、そこでメモリの解放を行うことが出来るようになる。
例えば、先ほどの例だと、次のようにしておく。
class RBuf < FFI::ManagedStruct # レイアウトはダミー layout(:dummy, :int) # オブジェクトが破壊されるタイミングでメモリの解放をする def self.release(ptr) MyLib.rbuf_delete(ptr) end end
今日はここまで!