Contents

Racket ffi

_今天花了些时间了解了Racket的FFI用法,只是初步的了解.所谓的FFI是指 foreign fuction interface.ok FFI让racket程序可以调用基于c的api而不用去写一行的c.

以下内容大部分来自对于这篇文章 的翻译,当然不会全是完全一样,其实我也有自己的私心—学好一样东西最好的办法就是将他教授给别人.

学习FFI最好的方式是去看文档 .但是对于一个初学者来说文档的信息量可能太大.

这篇教程会手把手地教会你关于FFI的一些使用,你需要准备的就是在你的电脑上安装好Drracket.

在开始之前,先声明一件事情.感谢Eli BarzilayDmitry Orlovsky的工作让我们可以在racket中使用FFI.如果你对于FFI的设计实现好奇的话可以参考这篇论文 .

这篇教程主要会使用 Cairo 图形库,主要是因为它是和Racket一起捆绑安装的.

我们的目标就是用racket复刻Cairo官网上的样例

  cairo_move_to (cr, 50.0, 75.0);
  cairo_line_to (cr, 200.0, 75.0);

  cairo_move_to (cr, 50.0, 125.0);
  cairo_line_to (cr, 200.0, 125.0);

  cairo_move_to (cr, 50.0, 175.0);
  cairo_line_to (cr, 200.0, 175.0);

  cairo_set_line_width (cr, 30.0);
  cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
  cairo_stroke (cr);

为了实现上述c代码所具有的功能,我们需要做一些准备工作.

#lang racket
(require racket/draw)
(define bt (make-bitmap 256 256))
(define bt-surface (send bt get-handle))

首先需要导入FFI

(require ffi/unsafe)

接下来就是导入Cairo库了

(define cairo-lib (ffi "libcairo"))

提取函数

在cairo的链接库中存在这样的api接口

/* NB: this is C code */
cairo_t * cairo_create (cairo_surface_t *target);

现在就可以用ffi将cairo_create转换为racket的函数,让我们可以在racket调用

(define cairo-create
    (get-ffi-obj "cairo_create" cairo-lib
                 (_fun _pointer -> _pointer)))

get-ffi-obj拿到函数的名字和它所在的库就可以拿到这个函数了,但是并不知道这个函数的调用参数.最后的那部分就是说明cairo_create的参数和返回值的 -> 的作用只是分隔参数的返回值

到这里对于ffi就有一个大致的了解了 _pointer是导入FFI之后就可使用的,对应的是c中的指针,类似的基本数据类型都有对应,例如_int ,_double等等. 只有_pointer类型的话也带来了一个问题,你很容易将不同的指针类型搞混,毕竟™都是指针.

插曲 类型安全

先定义一下指针类型,再去使用他们

(define-cpointer-type _cairo_t)
(define-cpointer-type _cairo_surface_t)

用新的指针类型来定义cairo_create

(define cairo-create
    (get-ffi-obj "cairo_create" cairo-lib
                 (_fun _cairo_surface_t -> _cairo_t)))

使用宏来减少重复劳动

有了上面的经验,接下来就可以实现其他的函数,将我们要用到的函数通通转化到Racket可以调用的形式

void cairo_move_to (cairo_t *cr, double x, double y);
void cairo_line_to (cairo_t *cr, double x, double y);
void cairo_set_line_width (cairo_t *cr, double width);
void cairo_set_line_cap (cairo_t *cr, cairo_line_cap_t line_cap);
void cairo_stroke (cairo_t *cr);

那么就从 cairo_move_to下手好了,就像之前那么做

(define cairo-move-to
    (get-ffi-obj
     "cairo_move_to"
     cairo-lib
     (_fun _pointer _double _double -> _void)))

似乎每次都要写一遍 (get-ffi-obj xxx cairo-lib (…)),实际上ffi提供了define-ffi-definer来简化函数的定义

 require ffi/unsafe/define)
 (define-ffi-definer define-cairo cairo-lib)
 (define-cairo cairo-move-to
     (_fun _cairo_t _double _double -> _void)
        #:c-id cairo_move_to)

如果你喜欢原来的函数名子的话定义还可以更加简单

(define-cairo cairo_move_to (_fun _cairo_t _double _double -> _void))

接下啦如法炮制

(define-cairo cairo-line-to
    (_fun _cairo_t _double _double -> _void)
    #:c-id cairo_line_to)
(define-cairo cairo-set-line-width
    (_fun _cairo_t _double -> _void)
    #:c-id cairo_set_line_width)
(define-cairo cairo-stroke
    (_fun _cairo_t -> _void)
    #:c-id cairo_stroke)

比较有意思的是cairo_set_line_cap,它的参数中出现了cairo_line_cap_t,这是一个枚举对象.Rackt的FFI可以让你方便的定义枚举对象

(define _cairo_line_cap_t
    (_enum '(butt round square)))
'))

这里的符号并不重要,因为他们会与一个整数对应(其实我不太明白,也许枚举类型会有一个编号,这些符号最终也只是这些编号的助记符).有了这个枚举类那么cairo_set_line_cap也就可以轻松的定义出来了


(define-cairo cairo-set-line-cap
    (_fun _cairo_t _cairo_line_cap_t -> _void)
    #:c-id cairo_set_line_cap)

ok,看看我们都完成了些什么

(cairo-move-to ctx 50.0 75.0)
(cairo-line-to ctx 200.0 75.0)
 
(cairo-move-to ctx 50.0 125.0)
(cairo-line-to ctx 200.0 125.0)
 
(cairo-move-to ctx 50.0 175.0)
(cairo-line-to ctx 200.0 175.0)
 
(cairo-set-line-width ctx 30.0)
(cairo-set-line-cap ctx 'round)
(cairo-stroke ctx)')

在这里我们用cairo的函数库已经绘制到bt中了,now看看结果吧

(require pict)
(linewidth 2 (frame (bitmap bt)))

没有什么意外的话会看到

现在将完整的代码放在下面

#lang racket
(require racket/draw
         racket/gui/base
         ffi/unsafe
         ffi/unsafe/define
         pict)

(define bt (make-bitmap 256 256))
(define bt-surface (send bt get-handle))
(define-cpointer-type _cairo_t)
(define-cpointer-type _cairo_surface_t)
(define cairo-lib (ffi-lib "libcairo"))
(define cairo-create (get-ffi-obj "cairo_create" cairo-lib
                                  (_fun  _cairo_surface_t -> _cairo_t)))
(define-ffi-definer define-cairo cairo-lib)

(define-cairo cairo-move-to
  (_fun _cairo_t _double _double -> _void)
  #:c-id cairo_move_to)
(define-cairo cairo-line-to
  (_fun _cairo_t _double _double -> _void)
  #:c-id cairo_line_to)
(define-cairo cairo-set-line-width
  (_fun _cairo_t _double -> _void)
  #:c-id cairo_set_line_width)
(define-cairo cairo-stroke
  (_fun _cairo_t -> _void)
  #:c-id cairo_stroke)

(define _cairo_line_cap_t
  (_enum '(butt round square)))

(define-cairo cairo-set-line-cap
  (_fun _cairo_t _cairo_line_cap_t -> _void)
  #:c-id cairo_set_line_cap)

(define ctx (cairo-create bt-surface))
(cairo-move-to ctx 50.0 75.0)
(cairo-line-to ctx 200.0 75.0)
(cairo-move-to ctx 50.0 125.0)
(cairo-line-to ctx 200.0 125.0)
(cairo-move-to ctx 50.0 175.0)
(cairo-line-to ctx 200.0 175.0)
(cairo-set-line-width ctx 30.0)
(cairo-set-line-cap ctx 'round)
(cairo-stroke ctx)

(linewidth 2 (frame (bitmap bt)))')'))

ok了,第一部分就这样.