Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

RubyでC拡張機能を書く方法(ステップバイステップガイド)

C拡張機能を書くことで、C言語からRubyと直接やり取りできるようになります。

特定の重要なメソッドをCの速度で最適化したい場合や、CライブラリとRubyの間にインターフェースを構築したい場合に非常に役立ちます。

なお、代替手段としてFFIモジュールを利用する方法もあります。

それでは、C拡張機能の書き方を見ていきましょう!

はじめてのC拡張機能

まず、extconf.rbというファイルを作成し、以下の内容を記述します。

require 'mkmf'

create_header
create_makefile 'foobar'

次にruby extconf.rbを実行すると、いくつかのファイル(Makefileextconf.h)が自動的に生成されます。これらのファイルは変更しないでください

続いてfoobar.cを作成します。このファイル名はextconf.rb内のcreate_makefileの行に由来しています。名前は自由に変更できますが、.cファイル名と一致させておく必要があります。

foobar.cの中に以下のコードを追加しましょう。

#include "ruby.h"
#include "extconf.h"

void Init_foobar()
{
  // Your C code goes here
}

これがC拡張機能の基本となるスケルトンです。ここで重要なのは、extconf.rbで宣言した名前にInit_という接頭辞を付けた関数名を使用しなければならない点です。この例ではInit_foobarとなります。

makeコマンドでコンパイルすると、soファイルが生成されます。このファイルはrequireを使ってRubyアプリケーションにロードできます。

CからRubyメソッドを定義する

Rubyコードから呼び出せるC関数を作成することができます。実は、Rubyの組み込みクラスやメソッドも同じ仕組みで動作しています。

すべてのRubyメソッドはVALUE型を返す必要があります。VALUEとは、Rubyオブジェクトを表す型のことです。

VALUE rb_return_nil() {
  return Qnil;
}

次に、この関数をRubyのクラスまたはモジュールにアタッチする必要があります。新しいクラスを作成してもよいですし、既存のものを利用しても構いません。

ここではモジュールを作成してみます

void Init_foobar() {
  VALUE mod = rb_define_module("RubyGuides");

  rb_define_method(mod, "return_nil", rb_return_nil, 0);
}

rb_define_methodメソッドを使うことで、C関数をこのモジュールにアタッチできます。

各引数の意味は以下の通りです

引数 説明
mod モジュールオブジェクト
"print_hello" Rubyメソッドの名前
rb_print_hello C関数の名前
0 このメソッドが受け取る引数の数(可変長引数の場合は-1)

もう一度makeを実行して拡張機能をコンパイルし、以下のように動作を確認してみましょう。

require 'foobar'

include RubyGuides
print_hello

これで、RubyからC関数を呼び出すことに成功しました!

CからRubyハッシュを作成する

実際の開発では、C側からRubyオブジェクトを作成・操作したい場面がよくあります。

そのような場合は、C APIを利用します。

例えば、ハッシュを作成して要素を追加するには、次のように書きます。

VALUE create_hash() {
  hash = rb_hash_new();

  rb_hash_aset(hash, rb_str_new2("test"), INT2FIX(1));
  return hash;
}

ここで注目すべき重要なポイントが2つあります。

1つ目はrb_str_new2関数です。これはRubyの文字列オブジェクトを新しく作成します。

2つ目はINT2FIXマクロです。これは通常のC整数値をRubyの整数オブジェクトに変換します。

この関数をrb_define_methodでRubyに公開すると、キーがtest、値が1の、ごく普通のRubyハッシュが取得できます。

なお、コードを変更するたびに拡張機能を再コンパイルすることをお忘れなく 🙂

文字列を扱う

C関数は引数を受け取ることもできます。

VALUE rb_print_length(VALUE self, VALUE str) {
  if (RB_TYPE_P(str, T_STRING) == 1) {
    return rb_sprintf("String length: %ld", RSTRING_LEN(str));
  }

  return Qnil;
}

void Init_foobar()
{
  rb_define_global_function("print_length", rb_print_length, 1);
}

このコードでは、VALUE(Rubyオブジェクト)を引数として受け取り、RB_TYPE_Pマクロでそれが文字列であることを確認しています。その後、RSTRING_LENマクロを使って文字列の長さを表示しています。

文字列データへのポインタが必要な場合は、RSTRING_PTRマクロを使用できます。

VALUE rb_print_data(VALUE self, VALUE str) {
  if (RB_TYPE_P(str, T_STRING) == 1) {
    return rb_sprintf("String length: %s", RSTRING_LEN(str));
  }

  return Qnil;
}

また、ここにselfというもう一つのパラメータがあることにも注目してください。これはメソッド呼び出しを受け取るレシーバーオブジェクトを表します。

Stringクラス自体にこのメソッドを定義したい場合は、次のように書きます。

void Init_foobar()
{
  rb_define_method(rb_cString, "print_length", rb_print_length, 0);
}

まとめ

今回は、RubyプログラムからCのパワーを最大限に活用できる「C拡張機能」の書き方を学びました。さらに、C関数をRubyに公開する方法や、文字列やハッシュといったRubyオブジェクトをC側から操作する方法についても理解できたはずです。

CからRubyとやり取りするためのその他の関数については、Rubyのソースコードや既存のC拡張機能のコードを読んで調べるのがおすすめです。

この記事が皆さんのお役に立てば嬉しいです。もし参考になったと感じたら、ぜひ友人たちにもシェアしてください!

  1. Rubyでポートスキャナーを作成する方法【TCP通信の基礎から実装まで】

    なぜポートスキャナーを自作したいと思うのでしょうか? ポートスキャナーの作成は、TCPプロトコルの基礎を学ぶ絶好の方法です。TCPは、HTTPやSSHを含むほとんどのインターネットプロトコルが利用するトランスポート層(伝送層)です。 また、Rubyのネットワークプログラミングの仕組みを深く理解するための良い演習にもなります。 それでは、ポートについての説明から始めましょう! ポートとは何か? 「ポート」というとき、私たちは実際には何を指しているのでしょうか?OS(オペレーティングシステム)レベルにおけるポートとは、単純に言えばプロセスに関連付けられた「ファイルディスクリプタ」のことです。 ファ

  2. Rubyでファイルを読み書きする方法を実例つきで徹底解説

    Rubyでファイルの読み書きができるようになると、ファイルからデータを抽出したり、新しいファイルを作成したり、必要な情報を素早く見つけたりできるようになります。この記事では、Rubyにおけるファイル操作の基本を、実際のコード例とともにわかりやすく解説します。 この記事で学べること Rubyでファイルを読み込む方法 Rubyでファイルに書き込む方法 覚えておきたいFileクラスのメソッド ディレクトリ操作の基礎 FileUtilsモジュールの使い方 まとめ それでは始めましょう! Rubyでファイルを読み込む方法 Rubyでファイルを読み込む基本的な流れは、次の3ステップです。 op