Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

kojix2
kojix2

Posted on

     

Mixing FFI, Fiddle, and C Extension in Ruby

Introduction

If you're working with Ruby and need to invoke a function written in C language, there are some convenient gems available:Ruby-FFI andFiddle.

Ruby-FFI has many features, handling most challenges you might encounter. Fiddle might seem a bit less convenient, but being an official Ruby gem, it is available from the start in most environments.

When You Want to Use Both FFI or Fiddle and C Extension

There can be situations where you want to rewrite certain parts of your Gem implemented with FFI or Fiddle into C extensions. Function calls using libffi are known to be nearly 100 times slower than that of native C extensions. If large numbers of calls need to be made with a demand for speed, you might want to consider rewriting the FFI-implemented function using C extensions.

The Method

Basic Principles

The main challenge here is determining how to handle the pointer of FFI or Fiddle's structure as an argument in a C extension function. The solution is straightforward: get the memory address from theFiddle::Pointer orFFI::Pointer Ruby objects.

Here you will learn how to write aC Extension function that takes FFI::Pointer as an argument, referring to thercairo gem.

Check for the existence of the constant FFI::Pointer

We begin by ensuring the constantFFI::Pointer is defined. This step verifies thatrequire "ffi" has been executed andFiddle::Pointer is available.

if(NIL_P(rb_cairo__cFFIPointer)){rb_raise(rb_eNotImpError,"%s: FFI::Pointer is required",rb_id2name(rb_frame_this_func()));}
Enter fullscreen modeExit fullscreen mode

Therb_cairo__cFFIPointer is pre-set inInit_cairo_private.

voidInit_cairo_private(void){// -- code omission --if(rb_const_defined(rb_cObject,rb_intern("FFI"))){rb_cairo__cFFIPointer=rb_const_get(rb_const_get(rb_cObject,rb_intern("FFI")),rb_intern("Pointer"));}else{rb_cairo__cFFIPointer=Qnil;}}
Enter fullscreen modeExit fullscreen mode

In the case of Fiddle, execute:

rb_const_get(rb_const_get(rb_cObject,rb_intern("Fiddle")),rb_intern("Pointer"));
Enter fullscreen modeExit fullscreen mode

Ensure Argument Type Consistency

After confirming the constant, we ensure the argument classes are consistent. AsFFI::Pointer andFiddle::Pointer obtain addresses using relatively common names -address andto_i respectively, performing a type check helps prevent errors.

if(!RTEST(rb_obj_is_kind_of(pointer,rb_cairo__cFFIPointer))){rb_raise(rb_eArgError,"must be FFI::Pointer: %s",rb_cairo__inspect(pointer));}
Enter fullscreen modeExit fullscreen mode

Acquiring the Address

With FFI, you can get the address with theaddress method.

# Ruby-FFIpt=FFI::MemoryPointer.new(:int)ppt.address
Enter fullscreen modeExit fullscreen mode

With Fiddle,to_i method helps in getting the address.

# Fiddlept=Fiddle::Pointer.new(Fiddle::SIZEOF_INT)ppt.to_i
Enter fullscreen modeExit fullscreen mode

In C extensions, these Ruby methods are invoked usingrb_funcall.

rb_funcall(ffi_pointer,rb_intern("address"),0)rb_funcall(fiddle_pointer,rb_intern("to_i"),0)
Enter fullscreen modeExit fullscreen mode

Call a C Function Using the Acquired Address as an Argument

The above Ruby code is executed within the C extension code.

VALUErb_cr_address;rb_cr_address=rb_funcall(pointer,rb_intern("address"),0);cr=NUM2PTR(rb_cr_address);cr_check_status(cr);
Enter fullscreen modeExit fullscreen mode

Here, theNUM2PTR macro is not provided byruby.h so you'll need to define it yourself:

#if SIZEOF_LONG == SIZEOF_VOIDP#  define PTR2NUM(x)   (ULONG2NUM((unsigned long)(x)))#  define NUM2PTR(x)   ((void *)(NUM2ULONG(x)))#else#  define PTR2NUM(x)   (ULL2NUM((unsigned long long)(x)))#  define NUM2PTR(x)   ((void *)(NUM2ULL(x)))#endif
Enter fullscreen modeExit fullscreen mode

Thecr_check_status function calls the native Cairo functioncairo_status_to_string. It's safe to insert a function like this in the middle.

Creating a Ruby Object

To create a Ruby object from the obtained address, do as follows:

rb_cr=rb_obj_alloc(self);cairo_reference(cr);RTYPEDDATA_DATA(rb_cr)=cr;rb_ivar_set(rb_cr,cr_id_surface,Qnil);
Enter fullscreen modeExit fullscreen mode

Userb_obj_alloc to create an instance of the class (self, in this case).cairo_reference() is a Cairo function that increases the reference count, which ensures Garbage Collection won't remove yourruby-FFI object.RTYPEDDATA_DATA is used to access the data of TypedData Objects directly. Lastly,rb_ivar_set sets an instance variable.

Basic Example

Let's walk through a basic example. For this,piyo.h andpiyo.c have been prepared as targets to create bindings.

piyo.h

#ifndef PIYO_H#define PIYO_H#include<stdio.h>typedefstructPiyo{intage;char*name;}Piyo;voiddisplayPiyoInfo(constPiyo*piyo);#endif
Enter fullscreen modeExit fullscreen mode

piyo.c

#include"piyo.h"voiddisplayPiyoInfo(constPiyo*piyo){printf("Name: %s\n",piyo->name);printf("Age: %d\n",piyo->age);}
Enter fullscreen modeExit fullscreen mode

Write the C extension so that the following code functions correctly:

require'fiddle/import'require_relative'./piyo.so'modulePiyoPiyo=Fiddle::Importer.struct(['int age','char* name'])endtori_name='piyoko'Piyo::Piyo.malloc(Fiddle::RUBY_FREE)do|piyo|piyo.age=100piyo.name=tori_namePiyo.display_info(piyo)end
Enter fullscreen modeExit fullscreen mode

Ruby C Extension

piyo_rb.c

#include"ruby.h"#include"piyo.h"#if SIZEOF_LONG == SIZEOF_VOIDP#define PTR2NUM(x) (ULONG2NUM((unsigned long)(x)))#define NUM2PTR(x) ((void *)(NUM2ULONG(x)))#else#define PTR2NUM(x) (ULL2NUM((unsigned long long)(x)))#define NUM2PTR(x) ((void *)(NUM2ULL(x)))#endifVALUErb_cFiddlePointer;VALUErb_display_info(VALUEself,VALUEpiyo){Piyo*ptr;VALUErb_address=rb_funcall(piyo,rb_intern("to_i"),0);ptr=NUM2PTR(rb_address);displayPiyoInfo(ptr);returnQnil;}voidInit_piyo(void){VALUEmPiyo=rb_define_module("Piyo");rb_define_singleton_method(mPiyo,"display_info",rb_display_info,1);}
Enter fullscreen modeExit fullscreen mode

Create Makefile with:

extconf.rb

require'mkmf'find_header('piyo.h',__dir__)create_makefile('piyo')
Enter fullscreen modeExit fullscreen mode

Compile with:

ruby extconf.rbmake
Enter fullscreen modeExit fullscreen mode

Execute with:

ruby test.rb
Enter fullscreen modeExit fullscreen mode

If everything runs correctly, you should see the output as:

Name: piyokoAge: 100
Enter fullscreen modeExit fullscreen mode

While this is a simple example and doesn't include every aspect, such as class definition verification and argument type checks, you will need to add these elements to transition it into a practical gem.

That's all for this post.


This article was translated from Japanese to English by a collaboration of ChatGPT, DeepL, and the author. The author, despite having the weakest command of English among the three, played a crucial role in providing instructions to ChatGPT and DeepL. In Japanese, 'Piyo' represents the chirping sound of a chick and is often used as a meta-syntax variable, following 'hoge' and 'fuga'.

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Ruby & Crystal & Bioinformatics
  • Location
    Japan
  • Joined

More fromkojix2

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp