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 FFI 入门教程的更多相关文章

  1. 《Ruby语言入门教程v1.0》学习笔记-01

    <Ruby语言入门教程v1.0> 编著:张开川 邮箱:kaichuan_zhang@126.com 想要学习ruby是因为公司的自动化测试使用到了ruby语言,但是公司关于ruby只给了一 ...

  2. 《Ruby语言入门教程v1.0》学习笔记-03

    10.09 第七章 7.1 模块 Ruby标准包里的 Math 模块提供了许多方法,比如:求平方根 sqrt ,使用的时候要这么写:模块名.方法名(参数).如:Math.sqrt( a*5+b ) M ...

  3. 《Ruby语言入门教程v1.0》学习笔记-02

    9.18 第四章 一切都是对象 这个章节的例子都举得很浅显易懂,而且作者的语言= =噗,委实生动有趣啊是~~ 4.1  两种思维方式 初期的编程思想是:以“如何做”为指导来编写代码.这时期的编程语言叫 ...

  4. Ruby入门教程和技巧

    转自:http://blog.csdn.net/cqfz123/article/details/1349050   Ruby真的比Java更好? Ruby On Rails 创始人:对Java 说再见 ...

  5. 转:几十种编程语言的快速入门教程- learnxinyminutes.com

    原文来自于:http://top.jobbole.com/15551/ 这家网站的名称是 Learn X in Y minutes,包括了几十种编程语言的快速学习入门教程.打开几种编程语言来看了一下, ...

  6. nodejs【伪】入门教程

    声明: 本文适合白的不能再白的小白 不要被标题误导,本文不会讲nodejs基础,只是本人学习流程和资料的一个整合 如果想找一大堆教程自己看,没有电梯,自己拉到文章最下方吧   一.nodejs是什么 ...

  7. TensorFlow 中文资源全集,官方网站,安装教程,入门教程,实战项目,学习路径。

    Awesome-TensorFlow-Chinese TensorFlow 中文资源全集,学习路径推荐: 官方网站,初步了解. 安装教程,安装之后跑起来. 入门教程,简单的模型学习和运行. 实战项目, ...

  8. laravel 中CSS 预编译语言 Sass 快速入门教程

    CSS 预编译语言概述 CSS 作为一门样式语言,语法简单,易于上手,但是由于不具备常规编程语言提供的变量.函数.继承等机制,因此很容易写出大量没有逻辑.难以复用和扩展的代码,在日常开发使用中,如果没 ...

  9. Apache Solr入门教程(初学者之旅)

    Apache Solr入门教程(初学者之旅) 写在前面:本文涉及solr入门的各方面,建议边思考边实践,相信能帮助你对solr有个清晰全面的了解并能简单实用. 在Apache Solr初学者教程的这个 ...

随机推荐

  1. JS继承模式粗探

    之前提到了JS中比较简单的设计模式,在各种设计模式中被最常使用的工具之一就是原型链的继承.作为OOP的特质之一——继承,今天主要谈谈JS中比较简单的继承方法. 最基础的原型链继承在这里就不复述了,主要 ...

  2. Web服务器常用设置

    1.Tomcat浏览目录 找到安装目录下的文件/conf/web.xml,  找到以下配置节,将parame-value设置为true即可 <init-param>             ...

  3. 分享一个简单程序(webApi+castle+Automapper+Ef+angular)

    前段时间在周末给朋友做了一个小程序,用来记录他们单位的一些调度信息(免费,无版权问题).把代码分享出来.整个程序没有做任何架构.但是麻雀虽小,用到的技术也没少.WebApi+Castle+AutoMa ...

  4. 人人都是 DBA(XI)I/O 信息收集脚本汇编

    什么?有个 SQL 执行了 8 秒! 哪里出了问题?臣妾不知道啊,得找 DBA 啊. DBA 人呢?离职了!!擦!!! 程序员在无处寻求帮助时,就得想办法自救,努力让自己变成 "伪 DBA& ...

  5. Spring RabbitMq概述

    Spring AMQP consists of a handful of modules, each represented by a JAR in the distribution. These m ...

  6. 项目八:团队项目——Alpha阶段项目总结

    1.项目的预期目标 a.完成游戏的基本功能 b.游戏难度的玩家手动调节 c.游戏能够良好的运行完成 与前期的需求分析对比: 第一点不同是游戏的难度调节,原来是想通过选择难度来调节的,但由于难度的分层上 ...

  7. IOS UIView 03- 自定义 Collection View 布局

    注:本人是翻译过来,并且加上本人的一点见解. 前言 UICollectionView 在 iOS6 中第一次被引入,也是 UIKit 视图类中的一颗新星.它和 UITableView 共享一套 API ...

  8. IOS 推送-客户端处理推送消息

    IOS 推送-客户端处理推送消息 1.推送调用顺序 APN push的消息到达后,UIApplicationDelegate有两个方法和处理消息有关: 1)application:didReceive ...

  9. servlet到底是什么?

    Servlet(Server Applet),全称Java Servlet,是用Java编写的服务器端程序.指任何实现了这个Servlet接口的类或者实现Servlet接口的类,当然或者实现接口继承方 ...

  10. 模拟淘宝登录和购物车功能:使用cookie记录登录名,下次登录时能够记得上次的登录名,使用cookie模拟购物车功能,使用session记住登录信息并验证是否登录,防止利用url打开网站,并实现退出登录功能

    Login <%@ page language="java" contentType="text/html; charset=UTF-8" pageEnc ...