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

ใช้ไลบรารี C จาก Ruby ผ่าน Fiddle - ไลบรารีมาตรฐาน Ruby ที่เป็นความลับที่สุด

Fiddle เป็นโมดูลที่ไม่ค่อยมีใครรู้จักซึ่งถูกเพิ่มลงในไลบรารีมาตรฐานของ Ruby ใน 1.9.x อนุญาตให้คุณโต้ตอบโดยตรงกับไลบรารี C จาก Ruby มันยังช่วยให้คุณตรวจสอบและแก้ไขตัวแปลทับทิมขณะทำงานได้อีกด้วย

ทำงานโดยปิด libffi ซึ่งเป็นไลบรารี C ยอดนิยมที่อนุญาตให้เขียนโค้ดในภาษาหนึ่งเพื่อเรียกเมธอดที่เขียนในอีกภาษาหนึ่งได้ ในกรณีที่คุณไม่เคยได้ยินมาก่อน "ffi" ย่อมาจาก "foreign function interface" และคุณไม่ได้จำกัดอยู่แค่ C เท่านั้น เมื่อคุณเรียนรู้ Fiddle แล้ว คุณสามารถใช้ไลบรารี่ที่เขียนด้วย Rust และภาษาอื่นๆ ที่รองรับได้

มาดูฟิดเดิ้ลกัน เราจะเริ่มด้วยตัวอย่างง่ายๆ แล้วปิดท้ายด้วยการเข้าถึง Arduino ผ่านพอร์ตอนุกรม คุณไม่จำเป็นต้องรู้มากขนาดนั้น C. ฉันสัญญา :)

ตัวอย่างง่ายๆ

ใน Ruby แบบเก่า คุณต้องกำหนดวิธีการเสมอก่อนจึงจะเรียกมันได้ มันเหมือนกันกับซอ คุณไม่สามารถเรียกฟังก์ชัน C ได้โดยตรง คุณต้องสร้าง wrapper สำหรับฟังก์ชัน C แทน จากนั้นเรียกใช้ wrapper

ในตัวอย่างด้านล่าง เรากำลังปิดฟังก์ชันลอการิทึมของ C โดยพื้นฐานแล้วเรากำลังทำซ้ำ Math.log ของ Ruby .

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

การควบคุมพอร์ตอนุกรม

ตกลง ในที่สุดคุณก็ซื้อ Arduinos ตัวหนึ่งที่คุณคิดจะซื้อมาหลายปีแล้ว คุณได้ส่งข้อความ "สวัสดีชาวโลก" ไปยังคอมพิวเตอร์ของคุณหนึ่งครั้งต่อวินาทีโดยใช้พอร์ตอนุกรม

ตอนนี้ คงจะดีมากถ้าคุณสามารถอ่านข้อมูลนั้นโดยใช้ Ruby และคุณสามารถอ่านจากพอร์ตอนุกรมได้จริงโดยใช้วิธี IO มาตรฐานของ Ruby แต่วิธีการ IO เหล่านี้ไม่อนุญาตให้เรากำหนดค่าอินพุตซีเรียลด้วยระดับความละเอียดที่เราต้องการ หากเราต้องการเป็นแฮ็กเกอร์ฮาร์ดแวร์ที่ไม่ยอมใครง่ายๆ

โชคดีที่ไลบรารีมาตรฐาน C มีฟังก์ชันทั้งหมดที่เราต้องการเพื่อใช้งานกับพอร์ตอนุกรม และด้วยการใช้ Fiddle เราสามารถเข้าถึงฟังก์ชัน C เหล่านี้ใน Ruby ได้

แน่นอนว่ามีอัญมณีที่ทำสิ่งนี้ให้คุณอยู่แล้ว เมื่อฉันทำงานกับโค้ดนี้ ฉันได้รับแรงบันดาลใจเล็กน้อยจากอัญมณีทับทิม แต่เป้าหมายของเราคือการเรียนรู้วิธีทำสิ่งนี้ด้วยตัวเราเอง

กำลังตรวจสอบ  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);

มีสิ่งสำคัญสามประการที่ควรสังเกตเกี่ยวกับรหัสนี้ อันดับแรกเรามี typedefs บางตัว จากนั้นเราก็มีโครงสร้างข้อมูลที่ใช้เก็บข้อมูลการกำหนดค่าสำหรับการเชื่อมต่อ ในที่สุดเราก็มีวิธีการที่เราจะใช้กัน

ด้วยการใช้ตัวนำเข้าของซอ เราเกือบจะสามารถคัดลอกส่วนต่างๆ เหล่านี้แบบต่อคำลงในโค้ด Ruby ของเราได้ มาจัดการทีละคน

พิมพ์นามแฝงและ typedefs

ใน C เป็นเรื่องปกติที่จะสร้างนามแฝงสำหรับประเภทข้อมูล ตัวอย่างเช่น ไฟล์ termios.h สร้างประเภทใหม่ที่เรียกว่า speed_t ซึ่งเป็นจำนวนเต็มแบบยาว ผู้นำเข้า Fiddle สามารถจัดการสิ่งเหล่านี้ผ่านคุณสมบัติ typealias

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

โครงสร้าง

C ไม่มีคลาสหรือโมดูล หากคุณต้องการจัดกลุ่มตัวแปรหลายๆ ตัวเข้าด้วยกัน ให้ใช้ struct Fiddle เป็นกลไกที่ดีในการทำงานกับ struct ใน ruby

ขั้นตอนแรกคือการกำหนดโครงสร้างในตัวนำเข้าซอ อย่างที่คุณเห็น เราเกือบจะสามารถคัดลอกและวางจากไฟล์ส่วนหัวได้แล้ว

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

ตอนนี้ เราสามารถสร้าง "ตัวอย่าง" ของ struct โดยใช้เมธอด malloc เราสามารถตั้งค่าและดึงค่าได้มาก  แบบเดียวกับที่ทำกับอินสแตนซ์คลาส Ruby ปกติ

s = Termios.malloc
s.c_iflag = 12345

รวมทุกอย่างเข้าด้วยกัน

หากคุณรวมสิ่งที่เราเพิ่งเรียนรู้เกี่ยวกับ typedefs และ struct เข้ากับสิ่งที่เราได้แสดงให้เห็นแล้วด้วยฟังก์ชัน เราสามารถรวบรวม wrapper ที่ใช้งานได้ของไลบรารี 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 จากนั้นรับ file descriptor เราใช้ฟังก์ชันเทอร์มิโอของเราเพื่อกำหนดค่าอุปกรณ์สำหรับการอ่าน แล้วเราค่อยอ่าน

ฉันกำลังใช้ตัวเลขมหัศจรรย์บางอย่างที่นี่ สิ่งเหล่านี้เป็นผลมาจากการรวมตัวเลือกการกำหนดค่าระดับบิตที่จำเป็นสำหรับการสื่อสารกับ 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

การทำงานของโปรแกรมอ่านซีเรียล

หากตอนนี้ฉันโหลด Arduino ด้วยโปรแกรมที่พิมพ์สวัสดีชาวโลกอย่างต่อเนื่อง ฉันสามารถใช้สคริปต์ทับทิมเพื่ออ่านมันได้

ใช้ไลบรารี C จาก Ruby ผ่าน Fiddle - ไลบรารีมาตรฐาน Ruby ที่เป็นความลับที่สุด โปรแกรม Arduino นี้เพียงแค่เขียน "hello world" ลงในซีเรียลครั้งแล้วครั้งเล่า

และนี่คือสิ่งที่ดูเหมือนเมื่อฉันเรียกใช้มอนิเตอร์แบบอนุกรม

ใช้ไลบรารี C จาก Ruby ผ่าน Fiddle - ไลบรารีมาตรฐาน Ruby ที่เป็นความลับที่สุด