園児ニア日記

RubyのFiddleやFFIでOpaque Pointerを扱う

とりあえず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/releaselibstringarray.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

はー、つら😇