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~