Computer >> 컴퓨터 >  >> 프로그램 작성 >> Ruby

Fiddle을 통해 Ruby의 모든 C 라이브러리를 사용하십시오. Ruby 표준 라이브러리는 가장 잘 비밀로 유지됩니다.

Fiddle은 1.9.x에서 Ruby의 표준 라이브러리에 추가된 잘 알려지지 않은 모듈입니다. Ruby의 C 라이브러리와 직접 상호 작용할 수 있습니다. Ruby 인터프리터가 실행되는 동안 검사하고 변경할 수도 있습니다.

한 언어로 작성된 코드가 다른 언어로 작성된 메소드를 호출할 수 있도록 하는 인기 있는 C 라이브러리인 libffi를 래핑하여 작동합니다. 들어 본 적이 없다면 "ffi"는 "외부 기능 인터페이스"를 의미합니다. 그리고 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 중 하나를 구입했습니다. 직렬 포트를 사용하여 초당 한 번씩 "hello world"라는 텍스트를 컴퓨터에 보냅니다.

이제 Ruby를 사용하여 해당 데이터를 읽을 수 있다면 정말 좋을 것입니다. 그리고 실제로 Ruby의 표준 IO 방식을 사용하여 직렬 포트에서 읽을 수 있습니다. 그러나 이러한 IO 방법을 사용하면 하드 코어 하드웨어 해커가 되는 경우 필요한 세분성 수준으로 직렬 입력을 구성할 수 없습니다.

다행히 C 표준 라이브러리는 직렬 포트로 작업하는 데 필요한 모든 기능을 제공합니다. 그리고 Fiddle을 사용하여 Ruby에서 이러한 C 함수에 액세스할 수 있습니다.

물론, 이미 당신을 위해 이것을 하는 보석이 있습니다. 이 코드를 작업할 때 rubyserial gem에서 약간의 영감을 얻었습니다. 하지만 우리의 목표는 이 작업을 스스로 수행하는 방법을 배우는 것입니다.

termios.h 검사

C에서 .h로 끝나는 파일 이름은 헤더 파일입니다. 여기에는 라이브러리가 타사 코드(저희 코드)에서 사용할 수 있도록 하는 모든 함수 및 상수 목록이 포함되어 있습니다. C 라이브러리를 래핑하는 첫 번째 단계는 이 파일을 찾아 열고 둘러보는 것입니다.

우리가 사용하는 라이브러리를 termio라고 합니다. 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);

이 코드에서 주의해야 할 세 가지 중요한 사항이 있습니다. 먼저 몇 가지 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은 루비에서 구조체로 작업하기 위한 좋은 메커니즘을 제공합니다.

첫 번째 단계는 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 및 struct에 대해 배운 내용을 함수로 이미 시연한 내용과 결합하면 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를 로드하면 내 루비 스크립트를 사용하여 읽을 수 있습니다.

Fiddle을 통해 Ruby의 모든 C 라이브러리를 사용하십시오. Ruby 표준 라이브러리는 가장 잘 비밀로 유지됩니다. 이 arduino 프로그램은 "hello world"를 직렬에 계속해서 기록합니다.

다음은 직렬 모니터를 실행할 때의 모습입니다.

Fiddle을 통해 Ruby의 모든 C 라이브러리를 사용하십시오. Ruby 표준 라이브러리는 가장 잘 비밀로 유지됩니다.