Ruby C Extensions

January 14, 2016 22:43


一个简单的 C 扩展

#include <stdio.h>
#include "ruby.h"

VALUE hello(VALUE self, VALUE name) {
  printf("c: hello %s\n", StringValuePtr(name));
  return INT2NUM(RSTRING_LEN(name));
} 

void Init_hello(void) {
  VALUE cHello;
  cHello = rb_define_module("Hello");

  rb_define_singleton_method(cHello, "hello", hello, 1);
} 

ruby.h

所有的 Ruby 值都是VALUE 类型的

可以通过 ruby.h 中的一些 function 来相互转换: StringValuePtr(value):就是把一个 Ruby 的 value 转换为一个指针 INT2NUM(int) 将一个 int 类型的值转成了Ruby 的 Bignum

通过 rbdefinexxx 来可以定义 module, class, method

同样,变量的 get/set,call ruby block,都是可行的: rbblockcall: 执行 ruby block rbdefinevariable/rbivarget/rbivarset: 定义/获取/设置 ruby 变量

Reference

编译 C 扩展

require 'mkmf'

create_makefile(‘hello’)                  

createmakefile(outpath) 将会在编译后输出 *.o 文件

上面代码中的就会输入 hello.o

在 Ruby 中调用 C 扩展

require './hello'

puts "length: #{Hello.hello("Ruby C Ext”)}"

require ‘./hello’ 将会加载编译好的 c 扩展 hello.o

因为我们在 C 中已经定义了 Hello 这个 module,并添加 了一个实例方法 hello,所以就可以直接使用了

调用/封装 C 库

#include "data_checker"
#include "ruby.h"

VALUE data_check(VALUE self, VALUE data) {
  if (data_checker(StringValuePtr(data))) {
    return Qture;
  } else { 
    return Qfalse;
  }
}

void Init_data_checker(void) {
  VALUE cDataChecker;
  cDataChecker = rb_define_module("DataChecker");

  rb_define_singleton_method(
    cDataChecker, "check", data_check, 1
  );
}                                           

使用体会

到这里,新手教程已经结束了,我们应该可以开开心心的调用C接口了吧

然而并不是这样的

一个 C 库中,五六个 struct 不算多吧?

你想调用总得包装一下这些 struct 吧?

一个 C 库中,我再依赖几个系统库很正常吧?

我一个系统库再有几个 struct 很正常吧?

那我们再包装下这些 struct,让 ruby 可以直接调用?


我的 function 需要调用者来分配内存!

我的 function 调用需要传入 function 指针做回调!


分配内存我也可以用 C 再封装一下

但 function 指针 ruby.h 中不提供啊! 难道我要去再去折腾 C 代码,自己实现匿名 function?


最后请允许我说脏话

玩你妹,这不是让我来写 C 了嘛

Ruby FFI 开始拯救世界

FFI 是一个 Ruby 的扩展,他可以加载动态库, 并绑定动态库中的 function,然后通过 Ruby 来调用

一个简单的 C dynamic library

hello.c

#include <stdio.h>
#include <string.h>

typedef void (*callback)(char *);

int hello(char *name, callback cb) {     
  printf("C: hello %s\n", name);
  cb("C String");
  return strlen(name);
}

Makefile

shared: clean .o
 gcc -shared -Wall -o libhello.so hello.o

clean:
 rm -rf hello.o libhello.so

.o:
 gcc -Wall -o hello.o -c hello.c 

make 后输入共享库 libhello.so

Ruby 通过 FFI 调用动态库

require 'ffi'

module Hello
  extend FFI::Library

  ffi_lib File.expand_path('../libhello.so', __FILE__)

  callback :cb, [:string], :void
  attach_function :hello, [:string, :cb], :int
end


result = Hello.hello(
  'FFI',
  Proc.new {|str| puts "C callback: #{str}" }
)
puts "return: #{result}"

运行后输出

C: hello FFI
C callback: C String
return: 3

到这里,我们已经不用再为了去使用一个 C 库而去写一几百上千行的 C 代码了, 通过简单的 Ruby 代码,就可以直接使用现有的 C 库了

FFI 使用体会

FFI 封装一个动态库时,基本上只要做两件事:定义接口 + 定义数据结构

但是

对于系统 struct 或是其它复杂的库中的 struct ,并不是那么简单的写几个 struct 就好了

像系统的 struct 还需要去查看源码,一般还需要根据 64 位或是 32 位系统分别去定义不同的 struct

并且很多 struct 在不同的系统上的定义是有区别的,可能还需要按不同的系统去定义不同的 struct , 像 OS X 与 Linux 的一些 struct 就需要分别去定义

自己简单封装的 Fuse

RBFuse 支持 Linux 64bit 与 OS X,初次上手比较折腾, 不过可以参考一下

当然,官方文档及 FFI 文档里给出的一些成品示例,还是不过的,建议多看看

Comments: