Ruby FFI 入门教程
FFI是一个可以让用户使用Ruby调用C代码的gem。如果你需要执行一些系统底层调用,或者做一些高性能运算的话,FFI是一个很不错的选择。
1. 安装
执行
gem install ffi即可。非常标准的安装过程,期间会做一些本地编译。
2. 超简易入门
现在我们已经可以调用C代码了。我们可以自己写一些C代码来调用,但是更简单的办法是直接调用C标准库里的东西。
我们试试看调用puts函数。随便创建一个文件
hello_world.rb:
require 'ffi' module Hello extend FFI::Library # 获得FFI::Library相关函数 ffi_lib FFI::Library::LIBC # 加载libc attach_function :puts, [ :string ], :int # 将puts方法挂到当前Hello模块上,指明入参和返回值的类型 end Hello.puts("Hello, World") # 现在可以调用puts这个模块方法了
运行得到输出结果:
Hello, World
Cool,调用C函数成功。
3. 调用自己的C函数
能调用C标准库是远远不够的。我们的目标是能够调用自己的C代码。
首先,我们写一个测试用的C源文件,比如叫做
c_base.c:
#include <string.h> int add(int a, int b){ return a+b; }
对,就是一个简单到不能再简单的C函数,做了个加法。
我们先将它编译:
gcc -c -fPIC c_base.c -o c_base.o gcc c_base.o -shared -o c_base.so
执行即可得到
c_base.so文件。
当然,为了方便起见,建议把这些放在一个
Makefile里面,直接用
make编译。
比如写成这样:
c_base.so: c_base.o gcc c_base.o -shared -o c_base.so c_base.o: c_base.c gcc -c -fPIC c_base.c -o c_base.o .PHONY: clean clean: rm *.o *.so
执行后一样可以得到
c_base.so文件。
然后我们就可以在Ruby里面调用了:
require 'ffi' module Hello extend FFI::Library ffi_lib File.join(File.dirname(__FILE__) + '/c_base.so') # 加载动态库,只要提供路径就行 attach_function :add, [:int,:int ], :int end res = Hello.add(10, 7) p res
运行得到输出结果:
17
好的!我们成功调用了自己的C函数!
4. 有关指针
到目前为止看起来不错。唯一需要注意的是
attach_function里面参数类型的指定。
我们用
:int来指定
int,用
:string来指定
char *类型(需要以\0结尾)
完整的类型映射关系可以参考: https://github.com/ffi/ffi/wiki/Types
之前我们在Hello World的例子中指定了:string来输出字符串,传给C代码的时候变成了char *类型。
但是对于这种指针操作实际上是有隐患的。考虑以下代码:
static char* my_name; void bad_set_my_name(char* name) { my_name = name; }
在这个代码里我们可以给
my_name设置值。
我们假设使用以下Ruby代码调用:
module Bar extend FFI::Library ffi_lib File.join(File.dirname(__FILE__) + '/set_name.so') # 加载动态库 attach_function :bad_set_my_name, [ :string ], :void end module Foo name = "Robin" Bar.bad_set_my_name(name) # 调用C代码 end
调用后我们可以认为现在C代码中的
my_name应该指向
Robin这个C字符串。
现在问题来了,如果Ruby的GC把name变量回收掉了,那么C里面的my_name指针将成为一个无效指针。另一方面由于这个指针是C在管理,因此Ruby也没有办法越俎代庖地去回收C里面的变量。因此这就成为了一个隐患。
还有一种更复杂的情况是,如果是在使用JRuby的情况下,由于JVM对于新生代使用的是复制GC算法,因此会使得堆里面的对象地址发生变化,这就使得即使没有回收,C里面的指针也可能会变成无效的。
解决方法也很简单,只需要让传给C的指针所指向的数据位置不要变就可以了。做法也很简单,只要把数据复制到native memory里面,然后把native memory里的地址传给C代码就可以了。
修改后的代码如下:
module Bar extend FFI::Library ffi_lib File.join(File.dirname(__FILE__) + '/set_name.so') attach_function :bad_set_my_name, [ :pointer ], :void # 注意类型改为了pointer end module Foo name = FFI::MemoryPointer.from_string("Robin") # 将Robin字符串拷贝到native memory上面去 Bar.bad_set_my_name(name) end
由于
FFI::MemoryPointer实际上代表了一个内存地址,相当于
void *,因此在
attach_function的时候需要修改入参类型为
:pointer。
反正传给C代码的指针在运行时都是纯粹的地址,
char *和
void *并没有区别,所以C代码不需要做修改。
- ruby使用format(或者%)格式化字符串String
- [Ruby] 关于字符串中特殊字符处理\n\t\r 的方法
- 如何在Ruby中编写微服务?
- 如何在Ruby中编写微服务?
- ruby 随笔
- 3 分钟轻松搭建 Ruby 项目自动化持续集成
- 3 分钟轻松搭建 Ruby 项目自动化持续集成
- ruby中的整数、浮点数、字符串之间的相互转换
- 使用ruby的gem安装gem包的SSL证书错误
- 开发新手最容易犯的50个 Ruby on Rails 错误(1)
- 开发新手最容易犯的50个 Ruby on Rails 错误(1)
- ruby字符串的encoding,force_encoding,encode,encode!转码(编码转换)
- [Ruby笔记]25.local scope 本地作用域
- 用rbenv给整个系统安装ruby(所有用户都可用)
- 关于Ruby的ARGV与gets语句同时使用的问题
- ruby学习笔记
- ruby 安装gem报错:`SSL_set_tlsext_host_name' was not declared in this scope
- 记录一次logstash 死锁问题
- ruby 生成随机数 和 随机字符串
- CocoaPods安装以及相关问题解决