Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

RubyからFiddle経由で任意のCライブラリを使用します-Ruby標準ライブラリは秘密にしておくのが最善です。

Fiddleは、1.9.xでRubyの標準ライブラリに追加されたあまり知られていないモジュールです。これにより、RubyのCライブラリと直接対話できます。実行中にrubyインタープリターを検査および変更することもできます。

これは、ある言語で記述されたコードが別の言語で記述されたメソッドを呼び出すことができる、人気のあるCライブラリであるlibffiをラップすることによって機能します。聞いたことがない方のために説明すると、「ffi」は「foreignfunctioninterface」の略です。そして、あなたはCだけに限定されていません。Fiddleを学ぶと、Rustやそれをサポートする他の言語で書かれたライブラリを使用できるようになります。

フィドルを見てみましょう。簡単な例から始めて、シリアルポート経由でArduinoにアクセスすることで終わります。あなたはそれほど多くのCを知る必要はありません。私は約束します。 :)

簡単な例

昔ながらのRubyでは、呼び出す前に常にメソッドを定義する必要があります。フィドルも同じです。 C関数を直接呼び出すことはできません。代わりに、C関数のラッパーを作成してから、ラッパーを呼び出す必要があります。

以下の例では、Cの対数関数をラップしています。基本的に、RubyのMath.logを複製しています。 。

require 'fiddle'

# We're going to "open" a library, so we have to tell Fiddle where
# it's located on disk. This example works on OSX Yosemite.
libm = Fiddle.dlopen('/usr/lib/libSystem.dylib')


# Create a wrapper for the c function "log". 
log = Fiddle::Function.new(
  libm['log'],            # Get the function from the math library we opened
  [Fiddle::TYPE_DOUBLE],  # It has one argument, a double, which is similar to ruby's Float
  Fiddle::TYPE_DOUBLE     # It returns a double
)

# call the c function via our wrapper
puts log.call(3.14159)
きれいにする

前の例は機能しますが、一種の冗長です。 100個の関数をラップしなければならない場合、雑然としたものがどのようになるか想像できると思います。そのため、Fiddleは優れたDSLを提供します。 Fiddle::Importerを介して公開されます ミックスイン。

このミックスインを使用すると、外部機能でいっぱいのモジュールを作成するのが簡単になります。以下の例では、いくつかの対数メソッドを含むモジュールを作成しています。

require 'fiddle'
require 'fiddle/import'

module Logs
  extend Fiddle::Importer
  dlload '/usr/lib/libSystem.dylib'
  extern 'double log(double)'
  extern 'double log10(double)'
  extern 'double log2(double)'
end

# We can call the external functions as if they were ruby methods!
puts Logs.log(10)   # 2.302585092994046
puts Logs.log10(10) # 1.0
puts Logs.log2(10)  # 3.321928094887362

シリアルポートの制御

さて、あなたはついにあなたが何年も購入することを考えていたそれらのArduinoの1つを購入しました。シリアルポートを使用して、「helloworld」というテキストを1秒に1回コンピュータに送信する必要があります。

さて、Rubyを使ってそのデータを読むことができれば本当に素晴らしいでしょう。また、Rubyの標準IOメソッドを使用してシリアルポートから実際に読み取ることができます。しかし、これらのIOメソッドでは、ハードコアハードウェアハッカーになる場合に必要なレベルの粒度でシリアル入力を構成することはできません。

幸い、C標準ライブラリは、シリアルポートでの作業に必要なすべての機能を提供します。 Fiddleを使用すると、RubyでこれらのC関数にアクセスできます。

もちろん、これを行う宝石はすでにあります。このコードに取り組んでいたとき、私はrubyserialgemから少しインスピレーションを得ました。しかし、私たちの目標は、このようなことを自分たちで行う方法を学ぶことです。

termios.hの調査

Cでは、.hで終わるファイル名はすべてヘッダーファイルです。これには、ライブラリがサードパーティのコード(私たちのコード)で利用できるようにするすべての関数と定数のリストが含まれています。 Cライブラリをラップする最初のステップは、このファイルを見つけて開き、見回すことです。

私たちが使用しているライブラリはtermiosと呼ばれます。 OSX Yosemiteでは、ヘッダーファイルは/usr/include/sys/termios.hにあります。 Linuxのどこかにあります。

termios.hは次のようになります。わかりやすくするために、かなり凝縮しました。

typedef unsigned long   tcflag_t;
typedef unsigned char   cc_t;
typedef unsigned long   speed_t;

struct termios {
    tcflag_t    c_iflag;    /* input flags */
    tcflag_t    c_oflag;    /* output flags */
    tcflag_t    c_cflag;    /* control flags */
    tcflag_t    c_lflag;    /* local flags */
    cc_t        c_cc[NCCS]; /* control chars */
    speed_t     c_ispeed;   /* input speed */
    speed_t     c_ospeed;   /* output speed */
};


int tcgetattr(int, struct termios *);
int tcsetattr(int, int, const struct termios *);
int tcflush(int, int);

このコードについて注意すべき3つの重要なことがあります。まず、いくつかのtypedefがあります。次に、接続の構成情報を保持するために使用されるデータ構造があります。最後に、使用するメソッドがあります。

fiddleのインポーターを使用すると、これらのセクションをほぼそのままRubyコードにコピーできます。一つずつ取り組んでいきましょう。

タイプエイリアスとtypedef

Cでは、データ型のエイリアスを作成するのが一般的です。たとえば、termios.hファイルは、単なる長整数であるspeed_tという新しいタイプを作成します。 Fiddleインポーターは、typealias機能を介してこれらを処理できます。

# equivalent to `typedef unsigned long tcflag_t;`
typealias "tcflag_t", "unsigned long"

構造体

Cにはクラスやモジュールがありません。一連の変数をグループ化する場合は、構造体を使用します。 Fiddleは、rubyで構造体を操作するための優れたメカニズムを提供します。

最初のステップは、Fiddleインポーターで構造体を定義することです。ご覧のとおり、ヘッダーファイルからコピーして貼り付けることがほぼ可能です。

Termios = struct [
  'tcflag_t c_iflag',
  'tcflag_t c_oflag',
  'tcflag_t c_cflag',
  'tcflag_t c_lflag',
  'cc_t         c_cc[20]',
  'speed_t  c_ispeed',
  'speed_t  c_ospeed',
]

これで、mallocメソッドを使用して構造体の「インスタンス」を作成できます。通常のRubyクラスインスタンスの場合とほぼ同じ方法で値を設定および取得できます。

s = Termios.malloc
s.c_iflag = 12345
すべてをまとめる

typedefと構造体について学んだことと、関数ですでに示したものを組み合わせると、termiosライブラリの実用的なラッパーをまとめることができます。

ラッパーには、シリアルポートを構成するために必要なメソッドが含まれています。残りはプレーンな古いルビーを使用します。

require 'fiddle'
require 'fiddle/import'

# Everything in this module was pretty much copied directly from 
# termios.h. In Yosemite it's at /usr/include/sys/termios.h
module Serial
  extend Fiddle::Importer
  dlload '/usr/lib/libSystem.dylib'

  # Type definitions
  typealias "tcflag_t", "unsigned long"
  typealias "speed_t", "unsigned long"
  typealias "cc_t", "char"

  # A structure which will hold configuratin data. Instantiate like
  # so: `Serial::Termios.malloc()`
  Termios = struct [
    'tcflag_t   c_iflag',
    'tcflag_t   c_oflag',
    'tcflag_t   c_cflag',
    'tcflag_t   c_lflag',
    'cc_t         c_cc[20]',
    'speed_t    c_ispeed',
    'speed_t    c_ospeed',
  ]

  # Functions for working with a serial device
  extern 'int   tcgetattr(int, struct termios*)'       # get the config for a serial device
  extern 'int   tcsetattr(int, int, struct termios*)'  # set the config for a serial device
  extern 'int   tcflush(int, int)'                     # flush all buffers in the device

end

ライブラリの使用

以下の例では、Rubyでシリアルデバイスを開き、そのファイル記述子を取得します。 termios関数を使用して、読み取り用にデバイスを構成します。次に読みます。

ここではいくつかのマジックナンバーを利用しています。これらは、arduinoとの通信に必要なビット単位の構成オプションの束を組み合わせた結果です。魔法の数字がどこから来ているのかに興味がある場合は、このブログ投稿をチェックしてください。

file = open("/dev/cu.wchusbserial1450", 'r')
fd = file.to_i

# Create a new instance of our config structure
config = Serial::Termios.malloc

# Load the default config options into our struct
Serial.tcgetattr(fd, config);

# Set config options important to the arduino. 
# I'm sorry for the magic numbers. 
config.c_ispeed = 9600;
config.c_ospeed = 9600;
config.c_cflag = 51968;
config.c_iflag = 0;
config.c_oflag = 0;

# wait for 12 characters to come in before read returns.
config.c_cc[17] = 12;
# no minimum time to wait before read returns 
config.c_cc[16] = 0;

# Save our configuration
Serial.tcsetattr(fd, 0, config);

# Wait 1 second for the arduino to reboot
sleep(1)

# Remove any existing serial input waiting to be read
Serial.tcflush(fd, 1)

buffer = file.read(12)
puts "#{ buffer.size } bytes: #{ buffer }"

file.close
動作中のシリアルリーダー

hello worldを継続的に出力するプログラムをarduinoにロードすると、rubyスクリプトを使用して読み取ることができます。

RubyからFiddle経由で任意のCライブラリを使用します-Ruby標準ライブラリは秘密にしておくのが最善です。 このarduinoプログラムは、「helloworld」をシリアルに何度も何度も書き込みます

シリアルモニターを実行すると、次のようになります。

RubyからFiddle経由で任意のCライブラリを使用します-Ruby標準ライブラリは秘密にしておくのが最善です。


  1. Ruby Grepメソッドの使用方法(例付き)

    grepについて話しましょう メソッド。 この方法で何ができますか? Grepを使用して、配列や範囲などの列挙可能なオブジェクトをフィルタリングできます。 「しかし、selectはすでにそれを行っています!」 はい。ただし、grepの動作は異なり、結果も異なります。 いくつかの例を見てみましょう。 RubyGrepメソッドの例 この配列が与えられた場合: objects = [a, b, c, 1, 2, 3, nil] grepを使用して文字列のみを取得できます : objects.grep(String) # [a, b, c] 整数のみ: objects.g

  2. Ruby Mapメソッドの使用方法(例付き)

    Mapは、配列、ハッシュ、範囲で使用できるRubyメソッドです。 マップの主な用途は、データを変換することです。 例 : 文字列の配列が与えられた場合、すべての文字列に目を通し、すべての文字を大文字にすることができます。 または、Userのリストがある場合 オブジェクト… 変換できます 対応するメールアドレス、電話番号、またはその他の属性のリストにそれらを追加します Userで定義 クラス。 これを行う方法を正確に見てみましょう! ルビーマップ構文 マップの構文は次のようになります: array = [a, b, c] array.map { |string| string.