RubyのFiddleやFFIでOpaque Pointerを扱う
2019/03/27
とりあえずRustで簡単なShared Libraryを用意する
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
pub type StringArray = Vec<CString>;
#[no_mangle]
pub extern fn string_array_new() -> *mut StringArray {
Box::into_raw(Box::new(StringArray::new()))
}
#[no_mangle]
pub extern fn string_array_destroy(string_array: *mut StringArray) {
unsafe {
Box::from_raw(string_array);
}
}
#[no_mangle]
pub extern fn string_array_add(string_array: *mut StringArray, value: *const c_char) {
unsafe {
(*string_array).push(CStr::from_ptr(value).to_owned());
}
}
#[no_mangle]
pub extern fn string_array_for_each(string_array: *const StringArray, callback: extern fn(*const c_char)) {
unsafe {
(*string_array).iter().for_each(|value| callback(value.as_ptr()));
}
}
↑のコードを cargo build --release
すると target/release
に libstringarray.so
ができるので適当なところにコピーする
FiddleでOpaque Pointerを扱う
とりあえずFiddleの公式ドキュメントに書いてあるstructを使ってみる
require 'fiddle/import'
module StringArrayFiddle
extend Fiddle::Importer
dlload './libstringarray.so'
StringArray = struct([])
end
$ bundle exec ruby main.rb
Traceback (most recent call last):
6: from main.rb:3:in `<main>'
5: from main.rb:7:in `<module:StringArrayFiddle>'
4: from /usr/lib/ruby/2.6.0/fiddle/import.rb:224:in `struct'
3: from /usr/lib/ruby/2.6.0/fiddle/struct.rb:64:in `create'
2: from /usr/lib/ruby/2.6.0/fiddle/struct.rb:112:in `size'
1: from /usr/lib/ruby/2.6.0/fiddle/pack.rb:55:in `align'
/usr/lib/ruby/2.6.0/fiddle/pack.rb:55:in `%': nil can't be coerced into Integer (TypeError)
これは動きません!
Fiddleのstruct
は最低でも一つは要素がないと使えないのです。
困りましたね。ドキュメントを見てもstruct
以外に構造体を宣言できそうなものがありません。
答え 宣言などせずにいきなり使う
宣言無しでいきなり使うと動きます。
require 'fiddle/import'
module StringArrayFiddle
extend Fiddle::Importer
dlload './libstringarray.so'
extern 'StringArray* string_array_new(void)'
extern 'void string_array_destroy(StringArray *string_array)'
extern 'void string_array_add(StringArray *string_array, const char* value)'
extern 'void string_array_for_each(const StringArray *string_array, void (*callback)(const char*))'
end
# 以下動作確認用コード
string_array = StringArrayFiddle.string_array_new
StringArrayFiddle.string_array_add(string_array, 'aaaa')
StringArrayFiddle.string_array_add(string_array, 'bbbb')
callback = Fiddle::Closure::BlockCaller.new(Fiddle::TYPE_VOID, [Fiddle::TYPE_VOIDP]) { |str| puts str }
StringArrayFiddle.string_array_for_each(string_array, callback)
StringArrayFiddle.string_array_destroy(string_array)
$ bundle exec ruby main.rb
aaaa
bbbb
FFIでOpaque Pointerを扱う
次はFFIでOpaque Pointerを扱うやり方
FFIの公式ドキュメントにあるように FFI::Struct
を継承してあげればできそうですね
require 'bundler/setup'
require 'ffi'
module StringArrayFFI
extend FFI::Library
ffi_lib './libstringarray.so'
class StringArray < FFI::Struct; end
attach_function :string_array_new, [], StringArray.by_ref
end
# 以下動作確認用コード
string_array = StringArrayFFI.string_array_new
$ bundle exec ruby main.rb
Traceback (most recent call last):
3: from main.rb:33:in `<main>'
2: from main.rb:33:in `string_array_new'
1: from main.rb:33:in `from_native'
main.rb:33:in `initialize': no Struct layout configured for StringArrayFFI::StringArray (RuntimeError)
これも動きません!
Fiddle同様FFIも最低でも一つは要素がないと使えないようです。
答え :pointerを使う
require 'bundler/setup'
require 'ffi'
module StringArrayFFI
extend FFI::Library
ffi_lib './libstringarray.so'
attach_function :string_array_new, [], :pointer
attach_function :string_array_destroy, [:pointer], :void
attach_function :string_array_add, [:pointer, :string], :void
callback :string_array_for_each_callback, [:string], :void
attach_function :string_array_for_each, [:pointer, :string_array_for_each_callback], :void
end
# 以下動作確認用コード
string_array = StringArrayFFI.string_array_new
StringArrayFFI.string_array_add(string_array, 'aaaa')
StringArrayFFI.string_array_add(string_array, 'bbbb')
StringArrayFFI.string_array_for_each(string_array, -> str { puts str })
StringArrayFFI.string_array_destroy(string_array)
$ bundle exec ruby main.rb
aaaa
bbbb