はじめに
Ruby-FFI とは
Ruby-FFIはlibffi
を使ってRubyでC言語のライブラリのバインディングを作成する有力なツールです。
Ruby-FFI はビットフィールド未対応だった
しかし、2021年7月現在、Ruby-FFIは構造体のビットフィールドをサポートしていません。ビットフィールド未対応であることはWikiにも記載されていますし、issueで開発者の方にも直接確認して、たしかに対応していないという返事をいただきました。
筆者の見聞によるとC言語の ビットフィールドへのサポートが進んでいないのはRuby言語に限った話ではないので、ある意味仕方がないことではあります。もしもビットフィールドが一部に使われているだけであれば、その都度カスタムメソッドを用意すれば十分だと思います。しかしターゲットとしているC言語のライブラリのあちこちでビットフィールドが利用されているとなるとその都度カスタムのメソッドを用意するのはとても手間がかかります。gemを作成して一気に解決したくなります。
私は ruby-htslib というバイオインフォマティックス関連のバインディングを作成しています。htslibはライブラリ全体でビットフィールドが多用されているためビットフィールドへの対応が避けられません。しかし、大量のカスタムメソッドを書くのはなんとかして避けたいと思いました。
ffi-bitfield GEMを作った
そこで、Ruby-FFIでビットフィールドを扱うためのgemを作成しました。
ビット演算については全然わからなかったので、ウンウン数時間かけて考えてなんとか初歩的な部分を理解しました。それでも自力で効率的なコードを書くことはできなかったので、Ruby-JPのslackに質問したり、スタックオーバーフローに質問したりしてほかの人にコードを書いてもらいました。こういうときにインターネットのコミュニティーは本当に頼りになると思います。
実装はピュアRubyで書かれており、[]=
メソッドや []
メソッドを挙動を変更するようになっています。C言語やFFIのレベルでどうこうするものではありません。
インストール
FFI以外の依存は特にありません。
gem install ffi-bitfield
使い方
require 'bit_structs
とすると FFI::BitStruct
と FFI::ManagedBitStruct
が使えるようになります。
require 'ffi/bit_structs'
require 'ffi/bit_struct' # FFI::BitStruct だけ使いたいとき
require 'ffi/managed_bit_struct' # FFI::ManagedBitStruct だけ使いたいとき
構造体とビットフィールドは次のような感じで定義します。
bit_field
と bit_fields
は同じ挙動で、メソッドの別名です。
require 'ffi/bit_struct'
class Struct1 < FFI::BitStruct
layout \
:a, :uint8,
:b, :uint8
bit_fields :a,
:a0, 1,
:a1, 1,
:a2, 1,
:a3, 1,
:a4, 1,
:a5, 1,
:a6, 1,
:a7, 1
bit_fields :b,
:b0, 1,
:b1, 1,
:b2, 2,
:b3, 4
end
あとは普通のRuby-FFIと全く同じです。
a = Struct1.new
読み込むときは
p a[:a0]
書き込むときは
a[:a0] = 1
とします。
一応テストもちょこっと書いたので、通常の用途ではほとんど問題なく使えると思います。
バグを見つけたらぜひ報告ください!
しかしなにぶん苦手な分野ですので、コーナーケースでは、ひょっとするとまだまだバグが残っているかも知れません。
もしもおかしな挙動を発見した方はgithubのissue欄にぜひ報告してください。
作者としては大変助かります。
気持ちとしてはお礼の品を差し上げたいぐらいですが、そういうわけにもいかないので、そのかわりに幸せな気持ちになるハトのgif画像を貼っておきます。
この記事は以上です。