C++で作るRuby拡張

# はじめに

この記事は[Ruby Advent Calendar 2019](https://qiita.com/advent-calendar/2019/ruby)の8日目の記事です。

昨日の記事は @Nymphium さんの[Rubyでもalgebraic effectsがしたい!](https://qiita.com/Nymphium/items/5df0894c5f8b01066a2b)でした。

この記事は、C++でのRuby拡張実装について、つらつらと書いている記事になります。

内容としては[TataraというRubyで型を使えるライブラリを作ってみた](https://qiita.com/S_H_/items/99730236c0e6acfada6b)で紹介した自作Ruby拡張を作るにあたって得たC++でのRuby拡張実装知見の記事になります。

# Ruby拡張って？

皆さんが普段使っているRuby(ここではCRubyのことです）はCによって実装されています。ですので、Cを使ってRubyの拡張機能を作成することもできます。

つまり、Cで既に作成されているライブラリなどをRuby拡張として作成することができるというメリットがあります。CでRuby拡張を実装した場合、Rubyで実装するよりも高速に処理できるケースもあるようです。

[TataraというRubyで型を使えるライブラリを作ってみた](https://qiita.com/S_H_/items/99730236c0e6acfada6b)で紹介している[Tatara](https://github.com/S-H-GAMELINKS/tatara)でベンチマークなどを試してみた感じでは、「現在のCRubyはかなり速い」という印象ですね。
RubyKaigi 2018の[Exploring Internal Ruby Through C Extensions](https://rubykaigi.org/2018/presentations/Yuryu.html#may31)でも「Rubyは速い」という話も出ています。
Rubyで実装できる内容のものであればRubyのほうが処理が速く、CやC++でわざわざ実装するほどでもないのかもしれません。

実際にCで拡張機能が実装されているgemとしては[sqlite3](https://github.com/sparklemotion/sqlite3-ruby)や[mysql2](https://github.com/brianmario/mysql2)などがあります。

またRustやC++でRubyの拡張機能を作成するケースもあります。

例えば最近面白いなぁと思ったのはRustでのRuby拡張を実装できる[Helix](https://github.com/tildeio/helix)ですね。Rustを使うことでCやC++よりも安全にRuby拡張を書くことができます。

また実装コード自体もかなり読みやすく以下のようなコードでクラスとメソッドを実装できます(※ HelixのREADMEより引用)。

```rust
ruby! {
    class Console {
        def log(string: String) {
            println!("LOG: {}", string);
        }
    }
}
```

ただHelix公式のチュートリアルではRails向けに拡張機能を実装する内容になっています。そのためRuby向けの拡張を作成する際のドキュメントがあまりなく、少し辛いところがあります。

実際にHelixでRuby拡張を作成しているものとしては以下の記事などがあります。

ref: [RubyからRustを呼び出すいくつかの方法のまとめ - Qiita](https://qiita.com/dd511805/items/fe603fe40b51307a8a9f)

ref: [Rustでgemを書く際のハマりどころ in 2017](https://blog.el-condor.net/entry/helix-2017)

ref: [Writing Ruby gems with Rust and Helix](https://blog.dnsimple.com/2017/05/writing-ruby-gems-with-rust-and-helix/)

またC++では[Rice](https://github.com/jasonroelofs/rice)や[Ext++](https://github.com/red-data-tools/extpp)などのRuby拡張を実装できるライブラリも存在しています。

RubyKaigi 2017では[Improve extension API: C++ as better language for extension](https://rubykaigi.org/2017/presentations/ktou.html)にてC++でのRuby拡張実装について紹介されています。

興味のある方はそちらも確認してみると良いでしょう。

今回はC++でのRuby拡張の実装方法について解説します。具体的には[Rice](https://github.com/jasonroelofs/rice)や[Ext++](https://github.com/red-data-tools/extpp)、C++のみでの実装方法などを解説していきます。

# つくるもの

今回は、`Hello`というクラスを作成し、`Hello Ruby Extension!`と画面に表示する`say`というメソッドを実装します。

具体的には以下のようなコードが実行できるRuby拡張を実装していきます。

```ruby
require 'hello'

Hello.new.say
# => "Hello Ruby Extension!"
```

今回は[Rice](https://github.com/jasonroelofs/rice)、[Ext++](https://github.com/red-data-tools/extpp)、C++でそれぞれ実装していきます。

今回の記事作成にあたって各ライブラリでの実装サンプルをGitHubに上げておきました。興味のある方はこちらも見ると良いかも。

[S-H-GAMELINKS/RubyAdventCalendarExtensionSample](https://github.com/S-H-GAMELINKS/RubyAdventCalendarExtensionSample)

# 実装
## Riceでの実装
### Riceとは？

[Rice](https://github.com/jasonroelofs/rice)とは、C++を使ってRuby拡張を簡単に作成できるライブラリになります。

RiceはgemとしてRubyGemsからインストールすることができます。

```bash
gem install rice
```

これでRiceが使えるようになります！

ちなみに、実際にRiceを使ったサンプルコードは以下のようになります。

```C++
#include <iostream>
#include <rice/Data_Type.hpp>
#include <rice/Constructor.hpp>

using namespace Rice;

class Hello {
    public:
        Hello() {};
        void say() { std::cout << "Hello Ruby Extension!" << std::endl; };
};

extern "C" {
    void Init_hello() {
        Data_Type<Hello> rb_cHello = define_class<Hello>("Hello")
            .define_constructor(Constructor<Hello>())
            .define_method("say", &Hello::say);
    }
}
```

このようにRiceを使う場合、非常に簡単にRuby拡張を作ることができます。

またC++のテンプレートなどを使って以下のようなコードを書くこともできます。

```cpp
template <class T>
class CppArray {
    public:
        CppArray<T>() {};
};

Data_Type<CppArray<int>> rb_cIntArray = define_class<CppArray<int>>("IntArray")
    .define_constructor(Constructor<CppArray<int>>());
```

Riceを使うメリットととしては、非常に簡単にC++でのRuby拡張を作ることができる点ですね。C++のライブラリなどをRubyで使えるようにするラッパーなどは、Riceを使って実装するといいかもしれません。

デメリットとしては、日本語のドキュメントもあまりないことと、開発自体があまり活発でない印象があることですね。

日本語で書かれた記事はあまり（Rice以外での実装とかはあったりする）なく、IBMの[Rice を使用して Ruby の拡張機能を C++ で作成する](https://www.ibm.com/developerworks/jp/opensource/library/os-extendruby/index.html)が日本語で唯一詳しく書かれたRiceのチュートリアルになりそうです。

英語が読める方であれば、[こちらのドキュメント](https://jasonroelofs.com/rice/)を読み解けばよいかと思います。

GitHubのリポジトリでのコミットログなどを見るた印象ではあまり開発が活発な印象はないです。最近、いくつかPull Requestが取り込まれてはいるようですが……。
そのため、Rice側の開発が打ち切られると辛いことになりそうな気配がありますね……。

とはいえ、大きな変更が入る可能性は少ないのでとりあえずC++でのRuby拡張を作る分には良いライブラリだと思います。

### 実装

それでは、Riceを使ってRuby拡張を実装してみましょう。

なにはともあれ、Riceをインストールしましょう。

```bash
gem install rice
```

インストールが無事終了した後は、`extconf.rb`というファイルを作成します。これはC++のコードをビルドする`Makefile`を自動生成するためのファイルになります。CでRuby拡張を作る場合も同様に`extconf.rb`を作成します。

```ruby:extconf.rb
require 'mkmf-rice'

create_makefile('hello')
```

`mkmf-rice`はRiceを使ってかかれたC++のソースをもとに`Makefile`を作成するためのライブラリになります。ちなみに、Cで拡張機能を実装する場合は`mkmf`というライブラリを読み込んで`Makefile`を自動生成していますね。

また`create_makefile`に渡している文字列がビルドされた拡張ライブラリの名前になります。

次に、`hello.cpp`を`extconf.rb`と同じ階層に作成します。

```cpp
#include <iostream>
#include <rice/Data_Type.hpp>
#include <rice/Constructor.hpp>

using namespace Rice;

class Hello {
    public:
        Hello() {};
        void say() { std::cout << "Hello Ruby Extension!" << std::endl; };
};

extern "C" {
    void Init_hello() {
        Data_Type<Hello> rb_cHello = define_class<Hello>("Hello")
            .define_constructor(Constructor<Hello>())
            .define_method("say", &Hello::say);
    }
}
```

軽くコードの解説をすると、以下の二行でRiceのヘッダーを読み込んでいます。

```cpp
#include <rice/Data_Type.hpp>
#include <rice/Constructor.hpp>
```

Riceでは`Data_Type`を使い、既存のクラスをもとにRuby向けにコンバートしています。

```cpp
Data_Type<Hello> rb_cHello = define_class<Hello>("Hello")
```

上記のコードではC++で定義した`Hello`クラスをRubyで呼び出す`Hello`というクラスに変換しています。

```cpp
.define_constructor(Constructor<Hello>())
```
`.define_constructor(Constructor<Hello>())`ではC++で定義した`Hello`クラスのコンストラクタ(Rubyでいうところのinitializeのようなもの）を使って、Rubyで`Hello`クラスのインスタンスを作成できるようにしています。
つまり、Rubyのinitializeを実装しています。

最後に`.define_method("say", &Hello::say);`で`say`というメソッドを`Hello`クラスに追加しています。

```cpp
.define_method("say", &Hello::say);
```

これでC++側での実装は完了です。

次に、`extconf.rb`を実行して`Makefile`を生成します。

```bash
ruby extconf.rb
# => Makefileを自動生成
```

あとは、`make`コマンドでビルドすれば`hello.o`と`hello.so`が生成されていると思います。

```bash
make
# => hello.o と hello.so が生成される
```

最後に、作成したRuby拡張を実際に動かしてみましょう。`hello.rb`を以下のように作成して実行してみましょう。

```ruby:hello.rb
require './hello.so'

Hello.new.say
```

```bash
ruby hello.rb
# => Hello Ruby Extension!
```

`Hello Ruby Extension!`と表示されていればOKです！

## Ext++での実装
### Ext++とは？

[Ext++](https://github.com/red-data-tools/extpp)はRice同様にC++を使って、Ruby拡張を作成できるライブラリです。

Ext++もRubyGemsで配布されているのでgemとしてインストールできます。

```bash
gem install extpp
```

Ext++での実装は以下のようになります。

```cpp
#include <iostream>
#include <ruby.hpp>

RB_BEGIN_DECLS

void Init_hello() {
    rb::Class klass("Hello");

    klass.define_method("initialize", [](VALUE rb_self, int argc, VALUE *argv) {
        return Qnil;
    });

    klass.define_method("say", [](VALUE rb_self) {
        std::cout << "Hello Ruby Extension!" << std::endl;
        return Qnil;
    });
}

RB_END_DECLS
```

Ext++ではC++のラムダ式を引数に渡して実装することができる点が特徴的です。そのためラムダ式をうまく使うことでRubyのメソッドとC++の実装を一度に書くことができます。

また、Ext++では`ruby.hpp`をインクルードするだけで良いところも便利です。Riceの場合、必要なヘッダーを個別に読み込まなければならず

Riceではラムダ式を使ってメソッドの定義などはできないため、ラムダ式でメソッドを定義したい人はExt++を使うと良いかもしれません

Ext++を使うメリットとしては、実装が一か所で済む点かなと思います。また開発者が日本の方(というか @ktou さん）ですので開発者本人にあって話が聴けるという点もメリットかもしれません。

デメリットとしては、サンプルのコードが一つしかなく、人によってはどのように実装を進めていけばいいのかが分かりにくい時がある点でしょうか？その点に関しては今後Pull Requestなどでサンプルコードを投げれたらと思っていますね。
また開発バージョンであり、今後のバージョンアップでは大きな変更も入る可能性もありそうです。

しかしながら、開発者本人に直接話を聞くことができそう（日本人からすると）なので採用するメリットはかなり大きいと思います。

またRiceと違い、Rubyの実装自体に近い実装コードを書くのでCRubyの実装を学んでみたいという人にもオススメかもしれませんね。

### 実装

それでは、Ext++を使ってRuby拡張を実装していきましょう。

まずはExt++をインストールします。

```bash
gem install extpp
```

インストール完了後、Riceでの実装の時と同じように`extconf.rb`を作成します。

```ruby:extconf.rb
require 'extpp'

create_makefile('hello')
```

Riceの時とおおよそ同じコードですね。違う点としては`mkmf-rice`ではなく、`extpp`を読み込んでいます。

次に、`hello.cpp`を`extconf.rb`と同じ階層に作成します。

```cpp:hello.cpp
#include <iostream>
#include <ruby.hpp>

RB_BEGIN_DECLS

void Init_hello() {
    rb::Class klass("Hello");

    klass.define_method("initialize", [](VALUE rb_self, int argc, VALUE *argv) {
        return Qnil;
    });

    klass.define_method("say", [](VALUE rb_self) {
        std::cout << "Hello Ruby Extension!" << std::endl;
        return Qnil;
    });
}

RB_END_DECLS
```

Ext++では`rb::Class`で新しいクラスを作成します。また、作成した`klass`と`define_method`を使うことで必要なメソッドを新しく定義しています。

`Qnil`はRubyでの`nil`を返しています。CRubyのメソッドなどで`nil`が返ってきているメソッドでは、子のように`return Qnil;`と書かれています。興味のある方は[Ruby Hack Challenge Holiday](https://rhc.connpass.com/event/155899/)に参加したり、GitHubの[ruby/ruby](https://github.com/ruby/ruby)のコードを読んでみると良いかもしれません。

あとは。`extconf.rb`を実行し、`Makefile`を生成します。

```bash
ruby extconf.rb
# => Makefileが生成される
```

その後、`make`で作成したRuby拡張をビルドします。

```bash
make
# => hello.o と hello.soが生成される
```

最後に`hello.rb`を以下のように作成し、実行してみましょう。

```ruby:hello.rb
require './hello.so'

Hello.new.say
```

```bash
ruby hello.rb
# => Hello Ruby Extension!
```

`Hello Ruby Extension!`と表示されていればOKです！

## C++での実装
### 実装

最後にC++でのみで作成するRuby拡張について紹介します。

まずは`extconf.rb`を作成します。

```ruby:extconf.rb
require "mkmf"

create_makefile("hello")
```

`mkmf`はCRubyに添付されているRuby拡張のためのMakefile作成ライブラリですね。

次に、`hello.cpp`を以下のように作成します。

```cpp
#include <ruby.h>
#include <iostream>

class Hello {
    public:
        Hello() {};
        ~Hello() {};
        void say() { std::cout << "Hello Ruby Extension!" << std::endl; };
};

struct WrapHello {
    Hello* hello;
};

static Hello* get_hello(VALUE self) {
    WrapHello *ptr;
    Data_Get_Struct(self, WrapHello, ptr);
    return ptr->hello;
}

static void wrap_hello_free(WrapHello *ptr) {
    delete ptr->hello;
    ruby_xfree(ptr);
}

static VALUE wrap_hello_alloc(VALUE klass) {
    auto *ptr = RB_ALLOC(WrapHello);
    ptr->hello = new(Hello);
    return Data_Wrap_Struct(klass, NULL, wrap_hello_free, ptr);
}

static VALUE wrap_hello_init(VALUE self) {
    return Qnil;
}

static VALUE wrap_hello_say(VALUE self) {
    get_hello(self)->say();
    return Qnil;
}

extern "C" {
    void Init_hello() {
        VALUE rb_cHello = rb_define_class("Hello", rb_cObject);

        rb_define_alloc_func(rb_cHello, wrap_hello_alloc);
        rb_define_private_method(rb_cHello, "initialize", RUBY_METHOD_FUNC(wrap_hello_init), 0);
        rb_define_method(rb_cHello, "say", RUBY_METHOD_FUNC(wrap_hello_say), 0);
    }
}
```

ポイントとしては`#include <ruby.h>`でRuby拡張の実装で使用するマクロや関数などを呼び出している点ですね。これがないとRuby拡張を実装することができません。

また`get_hello`関数はRubyのインスタンスを引数に受け取って、C++のインスタンスのポインタを返しています。この関数を使うことでC++のクラスのメソッドをラップ関数から呼び出して使うことができるようになります。
ちなみに、このコードでは`Data_Get_Struct`というマクロを使用していますが、`TypedData_Get_Struct`を使う方が良いようです（@ktou さんからのコメント参照）。
Ruby 2.1からは世代別GCが実装されており、古いオブジェクトから新しいオブジェクトへの参照を検知するためのライトバリアという仕組みが導入されています。このライトバリアに対応できているのが`Typed_Get_Struct`などになるようです。
ですので、新しくC++でRuby拡張を作成する場合は`TypedData_*`ではじまるマクロを使うと良いでしょう。

`wrap_hello_free`関数はRubyのGCが呼び出された際にメモリから解放する際の処理がかかれた関数になります。

`wrap_hello_alloc`はインスタンスを作成する際のアロケータになります。`wrap_hello_init`はRubyでの`initialize`になりますね。

あとは、`extconf.rb`を実行し、`make`を実行してビルドしてみましょう

```bash
ruby extconf.rb
# => Makfileが生成される

make
# => hello.o と hello.so が生成される
```

最後に、`hello.rb`を以下のように作成して実行しましょう。

```ruby:hello.rb
require './hello.so'

Hello.new.say
```

```bash
ruby hello.rb
# => Hello Ruby Extension!
```

`Hello Ruby Extension!`と表示されていればOKです！

# おわりに

C++でのRuby拡張についてRice、Ext++、C++それぞれでのでの実装を紹介しました。意外と簡単そうと思っていただければ幸いです。

あと今回の記事ではC++をベースに紹介しましたが、もちろんCでの実装を行う方法もあります。むしろ、そちらのほうが参考になる記事が多いので、Ruby拡張を作る際にはCで作ると良いかもしれません。

あと、この記事でRubyの実装に興味を持たれた方は[Ruby Hack Challenge Holiday](https://rhc.connpass.com/event/155899/)などに参加してみると良いかもしれません。

以前までは[Gitter](https://gitter.im/rubyhackchallenge/Lobby)にてRubyの実装の話などを訊くことができていましたが、[ruby-jp](https://ruby-jp.github.io/)が出来てからは`#rhc`チャンネルでやり取りされているようです。ですので、Rubyの実装に興味が湧いたら[ruby-jp](https://ruby-jp.github.io/)の`#rhc`チャンネルへ行きましょう！

意外と簡単にC++でもRubyの拡張機能を作ることができるので、今後もC++の良さげなライブラリなどをRuby向けに実装していきたいと思います。

# 参考記事

ref: [Rice](https://github.com/jasonroelofs/rice)
ref: [Ext++](https://github.com/red-data-tools/extpp)
ref: [Improve extension API: C++ as better language for extension](https://rubykaigi.org/2017/presentations/ktou.html)
ref: [Exploring Internal Ruby Through C Extensions](https://rubykaigi.org/2018/presentations/Yuryu.html#may31)
ref: [Rice を使用して Ruby の拡張機能を C++ で作成する](https://www.ibm.com/developerworks/jp/opensource/library/os-extendruby/index.html)
ref: [Rice - Ruby Interface for C++ Extensions](https://jasonroelofs.com/rice/)
ref: [ko1/rubyhackchallenge](https://github.com/ko1/rubyhackchallenge)
ref: [ruby/ruby](https://github.com/ruby/ruby)
ref: [C++言語で簡単なRuby拡張ライブラリを書いてみた](https://bl.ocks.org/mechamogera/3838813)
ref: [Rubyの拡張ライブラリの作り方](https://github.com/ruby/ruby/blob/master/doc/extension.ja.rdoc)
ref: [Rubyソースコード完全解説](http://i.loveruby.net/ja/rhg/book/)
ref: [TataraというRubyで型を使えるライブラリを作ってみた](https://qiita.com/S_H_/items/99730236c0e6acfada6b)
ref: [Tatara](https://github.com/S-H-GAMELINKS/tatara)
ref: [Rubyコミッター・笹田耕一に世代別インクリメンタルGCを発想したプロセスを聞いてみた](https://employment.en-japan.com/engineerhub/entry/2019/05/31/103000)
ref: [YARV Maniacs 【第 12 回】 インクリメンタル GC の導入](https://magazine.rubyist.net/articles/0048/0048-YARVManiacs.html)
ref: [Rubyにおけるライトバリアのないオブジェクトを考慮した世代別インクリメンタルGCの実装](https://www.atdot.net/~ko1/activities/sasada-ipsj-pro101.pdf)