CライブラリへのRubyバインディングの構築

先日、 libftdiライブラリへのバインディングを構築する必要がありました。libftdiライブラリは、 FTDIチップ(RS-232またはTTLレベルを介したシリアルデータ伝送をUSBバス信号に変換するためのチップです)。



バインディングを作成するために、 FFI拡張機能を選択しました。これにより、動的ライブラリをロードし、それらのバインダーを構築できます。



FFIにはいくつかの利点があり、その利点があります。



Rubyのリポジトリをバインドします



開始する



libftdiライブラリをロードするバインディングモジュールを作成します。

require 'ffi' # Represents libftdi ruby bindings. # End-user API represented by {Ftdi::Context} class. module Ftdi extend FFI::Library ffi_lib "libftdi" end
      
      





管理されていないリソース管理



libftdiの主な本質はそのコンテキストです。このコンテキストは、作業を開始するときに強調表示し、それに応じてリリースする必要があります。 FFI::ManagedStruct



クラスは、アンマネージリソースの自動収集を担当します。



  attach_function :ftdi_new, [ ], :pointer attach_function :ftdi_free, [ :pointer ], :void # Represents libftdi context and end-user API. # @example Open USB device # ctx = Ftdi::Context.new # begin # ctx.usb_open(0x0403, 0x6001) # begin # ctx.baudrate = 250000 # ensure # ctx.usb_close # end # rescue Ftdi::Error => e # $stderr.puts e.to_s # end class Context < FFI::ManagedStruct # layout skipped... # Initializes new libftdi context. # @raise [CannotInitializeContextError] libftdi cannot be initialized. def initialize ptr = Ftdi.ftdi_new raise CannotInitializeContextError.new if ptr.nil? super(ptr) end # Deinitialize and free an ftdi context. # @return [NilClass] nil def self.release(p) Ftdi.ftdi_free(p) nil end end
      
      





FFI :: ManagedStructコンストラクターは、指定されたレイアウト(ネイティブビューからFFIビューへの構造変換のマップ)によってマーシャリングされる構造へのポインターを受け入れます。 コンストラクターでは、ftdi_newの呼び出し(基本的にmallocを使用)でポインターを取得し、スーパークラスに渡します。



ガベージを収集するとき、ネイティブクラスへのパラメーターポインターを使用してreleaseクラスメソッドが呼び出され、そのクラスで解放されます。



APIを形成します



すべてのライブラリ呼び出しはコンテキストで機能するため、すべてのAPIコンテキストメソッドを作成し、libftdiコンテキストへのポインターを返すctxメソッドを作成して、これらの呼び出しの呼び出しを簡素化します。



ほとんどのlibftdi関数は、結果がゼロより小さい場合にエラーを示す符号付き整数を返します。 したがって、関数呼び出しの結果を解析し、問題が発生した場合に例外をスローするためのヘルパーを作成すると便利です。

  private def ctx self.to_ptr end def check_result(status_code) if status_code < 0 raise StatusCodeError.new(status_code, error_string) end nil end
      
      





ここで、 error_string



は、libftdiコンテキストからエラーメッセージを受け取るメソッドです。



次に、たとえば、ポートオプションの列挙を作成し、 ftdi_set_interface



関数ftdi_set_interface



バインドしftdi_set_interface



。 私たちが踊る場所:

 enum ftdi_interface { INTERFACE_ANY = 0, INTERFACE_A = 1, INTERFACE_B = 2, INTERFACE_C = 3, INTERFACE_D = 4 }; int ftdi_set_interface(struct ftdi_context *ftdi, enum ftdi_interface interface);
      
      





そして、私たちは何を得ます:

  # Port interface for chips with multiple interfaces. # @see Ftdi::Context#interface= Interface = enum(:interface_any, :interface_a, :interface_b, :interface_c, :interface_d) attach_function :ftdi_set_interface, [ :pointer, Interface ], :int class Context # ... # Open selected channels on a chip, otherwise use first channel. # @param [Interface] new_interface Interface to use for FT2232C/2232H/4232H chips. # @raise [StatusCodeError] libftdi reports error. # @return [Interface] New interface. def interface=(new_interface) check_result(Ftdi.ftdi_set_interface(ctx, new_interface)) new_interface end ... end
      
      







バイト配列を使用する



ASCIIZ文字列の操作は簡単ですが(type :string



)、それらを使用してバイトの配列を転送しようとすると、FFIマーシャラーが最初のゼロバイトでつまずくため、失敗に終わります。



バイトの配列を転送するには、type :pointer



を使用し:pointer



。これは、FFI :: MemoryPointer(メモリ内の対応するバッファの割り当てと充填)を通じて作成します。



  attach_function :ftdi_write_data, [ :pointer, :pointer, :int ], :int class Context # ... # Writes data. # @param [String, Array] bytes String or array of integers that will be interpreted as bytes using pack('c*'). # @return [Fixnum] Number of written bytes. # @raise [StatusCodeError] libftdi reports error. def write_data(bytes) bytes = bytes.pack('c*') if bytes.respond_to?(:pack) size = bytes.respond_to?(:bytesize) ? bytes.bytesize : bytes.size mem_buf = FFI::MemoryPointer.new(:char, size) mem_buf.put_bytes(0, bytes) bytes_written = Ftdi.ftdi_write_data(ctx, mem_buf, size) check_result(bytes_written) bytes_written end end
      
      







ご覧のとおり、バインダーの作成は簡単な作業でした。



構築を自動化する場合は、 SWIGの方向を確認することをお勧めします。



All Articles