Ruby 使用 ruby::fiddle 封装动态链接库

wujian_hit · 2013年07月30日 · 最后由 blackanger 回复于 2013年07月30日 · 5155 次阅读

environment require:ruby2.0.0 & libffi

在 ruby2.0.0 中 stdlib 里内置了 fiddle。fiddle 是 ruby 语言用来包装其他语言函数接口的一个扩展。fiddle is based on libffi,libffi 是一个非常流行的 c 库,它允许用一种语言代码包装另一语言的接口,实现跨语言的调用。

1. 环境准备 Linux 下初次使用 fiddle 需要安装 libffi_dev
Windows 下只要在安装 ruby 时勾选安装 DevKit 就可以使用

sudo apt-get install libffi_dev
rvm reinstall 2.0.0

2. 一个简单的例子调用/lib/i386-linux-gnu/libm.so.6中的 floor 函数

函数原型:double floor(double)
ruby 包装:

require 'fiddle'
libm = Fiddle.dlopen('/lib/i386-linux-gnu/libm.so.6')
floor = Fiddle::Function.new(
    libm['floor'],
    [Fiddle::TYPE_DOUBLE],
    Fiddle::TYPE_DOUBLE
    )
#使用
puts floor.call(3.14159)

3. 代码解释

Fiddle.dlopen(#{Path}),与 c 中调用动态链接库方法名相同 dlopen

Fiddle::Function.new(函数名,参数,返回值)

多个参数则:

so = Fiddle.dlopen('×××.so') 
func = Fiddle::Function.new(
    so['funcname'],

    [Fiddle::TYPE_DOUBLE,
     Fiddle::TYPE_INT,
     Fiddle::TYPE_VOIDP,
     Fiddle::TYPE_CHAR],

     Fiddle::TYPE_VOIDP
    )

Fiddle 中定义了 C 对应的数据类型TYPE_CHAR,TYPE_INT,TYPE_FLOAT,TYPE_DOUBLE,TYPE_LONG,布尔型和 int 型相同,指针则一律使用TYPE_VOIDP,结构体、共用体也有对应的类可供使用。详见:ruby-rdoc-Fiddle 文档

4. 写一个相对复杂的例子 (结构体、内存操作)

c 语言中我们经常会动态 malloc 内存,甚至有时会组织返回一块内存,来实现大块数据的返回和传递,现在有一段 c 语言实现:

typedef struct st_term{
    int id;
    int weight;
    float detail[5];
    char used;
} term;

term * get_terms(char* string, int* strlength){
    term *pterm;
    int count;
    count = *strlength;
    term *p = malloc( count * sizeof(term) );
    pterm = p;

    for (int i = 0; i < count; ++i)
    {
        pterm->id = i;
        ......
        pterm++;
    }

    return p;
} 

函数get_terms传入一个字符串 string,和一个int* strlength,返回一块内存,这块内存里顺序放着一个个结构体 term,每一个 term 里放着函数处理后的结果。现在我们用 ruby 来封装这个函数。为了便于 ruby 操作,我们将返回的这块内存封装成一个 array,array 中的元素是一个一个的 term 结构体。注意:传入的两个参数都是指针,第一个char*型我们可以直接传入一个 ruby 的 string 类即可,第二个int*则需要我们手工进行包装,将一个 Fixnum 类转变为一个int*int*其实就是一个内存地址。

1.定义结构体
2.提取链接库接口
3.定义 ruby 函数


require 'fiddle'
require 'fiddle/struct'
require 'fiddle/import'
include Fiddle::CParser
include Fiddle::Importer

###结构体定义
 term = struct[ 'int id' , 
                'int weight' , 
                'float  detail[5]' , 
                'char used' ]

###打开链接库
 lib = Fiddle.dlopen(File.expand_path("./libname.so", __FILE__))

###将链接库中的函数入口封装为Fiddle::Function型
 get_terms_rb = Fiddle::Function.new(                                  
      lib['get_terms'],
      [Fiddle::TYPE_VOIDP,Fiddle::TYPE_VOIDP],
      Fiddle::TYPE_VOIDP
  )

###对应的ruby函数

def get_terms(text)

     #获取传入第二个参数值 (字符串长度 Fixnum型)
    resultCount = text.length
     #将其类变为Pointer型   
    pResultCount = Fiddle::Pointer.to_ptr(resultCount)          

     #传参调用Fiddle::Function 返回值为c指针p(p的值为内存地址)
    p = get_terms_rb.call(text, pResultCount.ref.to_i)
     #将c指针p变为Pointer型            
    pVecResult = Fiddle::Pointer.new(p.to_i)                           
     #初始化一array
    terms_list = [] 
     #将指向第一块term的Pointer压入array         
    terms_list << term.new(pVecResult)#后面详解term.new函数  

     #逐个压入指向剩余term的Pointer
    for i in 1...resultCount  do
          #Pointer向后移动一个term.size 大小
        terms_list << term.new(pVecResult += term.size)  
    end
    return terms_list

end

值得注意的是:term.new 这个函数,传入一个 Pointer 实例,返回一个 term 实例。功能是将这个 Pointer 包装成一个 term 结构体,就像 c 中p=(term*)p将一个char* p强制转化为term*类型一样。这样就可以对这块内存,以 term 结构体的方式进行有关的操作了。比如通过 term.XXX 访问 term 的内的字段,具体使用看下面:

#调用下get_terms
    tempArray=get_terms("我爱你中国")
    tempArray.each do |term|
      puts "word_ID:#{term.id}"
    end

ok, that`all~

学习了。

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册