Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

เข้าถึง C Library ใดๆ จาก Ruby ด้วย Fiddle: ปลดล็อกความสามารถแอบแฝงของ Ruby

Fiddle เป็น module น่ารู้น้อยที่เพิ่มใน Ruby standard library ใน 1.9.x ให้ interact กับ C libraries จาก Ruby โดยตรง ยัง inspect และ alter ruby interpreter ขณะรันได้

มัน wrap libffi C library ยอดนิยม ให้ code ภาษาหนึ่ง call methods ภาษาอื่น "ffi" = "foreign function interface" ไม่จำกัดแค่ C รู้ Fiddle แล้วใช้ libraries Rust และภาษาอื่นๆ ที่รองรับได้

มาดู Fiddle เริ่ม simple example จน access Arduino ผ่าน serial port ไม่ต้องรู้ C มาก สัญญา :)

A Simple Example

Plain-old Ruby ต้อง define method ก่อน call เหมือนกันกับ Fiddle ไม่ได้ call C function ตรงๆ ต้องสร้าง wrapper สำหรับ C function เรียก wrapper

ตัวอย่าง wrap C's logarithm function 基本上 duplicate Ruby's 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) 

Making It Pretty

ตัวอย่างก่อน work แต่ verbose คลาย cluttered ถ้าต้อง wrap 100 functions Fiddle ให้ DSL สวยผ่าน Fiddle::Importer mixin

Mixin นี้ทำให้สร้าง module เต็ม external functions ง่าย ตัวอย่างสร้าง module มี logarithm methods หลายตัว:

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 

Controlling the Serial Port

Ok สุดท้ายซื้อ Arduino ที่คิดหลายปี โปรแกรมส่ง "hello world" ทุกวินาทีทาง serial port

ดีถ้าอ่าน data ด้วย Ruby Ruby standard IO methods อ่าน serial port ได้ แต่มันไม่ให้ configure serial input ด้วย granularity ที่ต้องการถ้าอยากเป็น hardcore hardware hackers

C standard library มี functions ทั้งหมดสำหรับ serial port Fiddle เรียก C functions นี้จาก Ruby ได้

มี gems ทำให้แล้ว ตอนเขียน code นี้ refer rubyserial gem เล็กน้อย แต่เป้าหมายเรียนรู้ทำเอง

Examining termios.h

ใน C filename ลงท้าย .h = header file มี list functions และ constants ที่ library ทำ available ให้ third-party code (our code) ขั้นแรก wrap C library หา file นี้ เปิด ดู

Library = termios บน OSX Yosemite header file อยู่ /usr/include/sys/termios.h Linux อยู่ที่อื่น

termios.h 长这样 condensed สำหรับ clarity

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 สิ่งสำคัญ: typedefs, data structure เก็บ config connection, methods ที่จะใช้

Fiddle importer copy sections เหล่านี้ verbatim เข้า Ruby code ได้เกือบหมด มาทำทีละอย่าง

Type Aliases and Typedefs

C สร้าง aliases สำหรับ data types บ่อย termios.h สร้าง speed_t = long integer Fiddle importer handle typedefs ผ่าน typealias feature

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

Structs

C ไม่มี classes/modules group variables ใช้ struct Fiddle ให้ mechanism สวยทำงานกับ structs ใน Ruby

ขั้นแรก define struct ใน Fiddle importer copy-paste จาก header file ได้เกือบหมด

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', ] 

สร้าง "instance" ของ struct ด้วย malloc method set/retrieve values เหมือน normal Ruby class instance

s = Termios.malloc s.c_iflag = 12345 

Putting It All Together

รวม typedefs, structs, functions ที่เรียนรู้ wrapper termios library ที่ทำงานได้

Wrapper มีแค่ methods configure serial port ใช้ plain Ruby ส่วนที่เหลือ

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 

Using the Library

ตัวอย่างเปิด serial device ใน Ruby ได้ file descriptor ใช้ termios functions configure device อ่าน

Magic numbers จากการ combine bitwise configuration options สำหรับ Arduino สนใจ source ดู blog post นี้

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 

The Serial Reader in Action

Load arduino โปรแกรมพิมพ์ hello world ตลอด ใช้ ruby script อ่าน

เข้าถึง C Library ใดๆ จาก Ruby ด้วย Fiddle: ปลดล็อกความสามารถแอบแฝงของ Ruby Arduino program แค่เขียน "hello world" ไป serial ซ้ำๆ

รัน serial monitor 长这样:

เข้าถึง C Library ใดๆ จาก Ruby ด้วย Fiddle: ปลดล็อกความสามารถแอบแฝงของ Ruby