C++で実装した class を rb++ を使って、ruby 拡張ライブラリにして、ruby から呼び出してみる


## まずは、適当なC++クラスを実装

```include/test.h
#ifndef __TEST_H__
#define __TEST_H__

class Test{
  public:
    Test();
    ~Test();
    void sample_method();
};
#endif
```

```src/test.cpp
#include <test.h>
#include <iostream>

Test::Test(){ std::cout << "call constructor" << std::endl; }
Test::~Test(){ std::cout << "calll destructor" << std::endl; }
void Test::sample_method(){ std::cout << "call sample_method" << std::endl; }

/* 動作確認用 */
int main(){
  Test* test = new Test();
  test->sample_method();
  delete test;
}
```

### コンパイル

```
$ g++ src/test.cpp -I./include/
```

### 動作確認

```
$ ./a.out
call constructor
call sample_method
calll destructor
```

想定通り

## rb++の導入

### rb++って?

[rb++](http://rbplusplus.rubyforge.org/)

ruby から C++ のクラス を拡張ライブラリとして呼び出す場合、結構面倒なラッパーを用意して、C++ のクラスと ruby の橋渡しをしてあげないといけない

- [第2部 クラスおよびそのメンバー関数 - tuedaの日記](http://d.hatena.ne.jp/tueda_wolf/20091205/p3) 


rb++ を使うと、ラッパーを自動的に作ってくれる


### bundlerで導入

```
$ bundle init
$ cat Gemfile
source "http://rubygems.org"

gem 'rbplusplus'

$ bundle install --path vendor/gems --binstubs=vendor/bin
```

#### --enable-shared をつけてコンパイルした ruby ではないと言われる

```
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    /Users/kasei_san/.rbenv/versions/2.1.0/bin/ruby extconf.rb
extconf.rb:23:in `<main>': Unfortunately Rice does not work against a Ruby without any shared libraries. (RuntimeError)
You'll need to rebuild Ruby with --enable-shared to use this library.

If you're on rvm:   rvm reinstall [version] -- --enable-shared
If you're on rbenv: CONFIGURE_OPTS="--enable-shared" rbenv install [version]
If this is a host environment like Heroku you'll need to contact their support.

extconf failed, exit code 1

Gem files will remain installed in /Users/kasei_san/Dropbox/work/cpp_ruby/vendor/bundle/ruby/2.1.0/gems/rice-1.6.0 for inspection.
Results logged to /Users/kasei_san/Dropbox/work/cpp_ruby/vendor/bundle/ruby/2.1.0/extensions/x86_64-darwin-13/2.1.0-static/rice-1.6.0/gem_make.out
An error occurred while installing rice (1.6.0), and Bundler cannot continue.
Make sure that `gem install rice -v '1.6.0'` succeeds before bundling.
```


```
--enable-sharedというのはrubyコマンドのほとんどをダイナミック リンクライブラリ（libruby.so）として外に出すオプション
```

- [rubyビルドガイド](http://i.loveruby.net/ja/rhg/cd/build.html)

enable-shared をつけてインストールすればOK

```
$ CONFIGURE_OPTS="--enable-shared" rbenv install 2.1.1
```



#### aclocal が無いと言われる

```
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    /Users/kasei_san/.rbenv/versions/2.1.1/bin/ruby extconf.rb
autoreconf: Entering directory `.'
autoreconf: configure.ac: not using Gettext
autoreconf: running: aclocal  --output=aclocal.m4t
Can't exec "aclocal": No such file or directory at /usr/local/Cellar/autoconf/2.69/share/autoconf/Autom4te/FileUtils.pm line 326.
autoreconf: failed to run aclocal: No such file or directory
post-autoconf.rb:6:in `initialize': No such file or directory @ rb_sysopen - configure (Errno::ENOENT)
        from post-autoconf.rb:6:in `open'
        from post-autoconf.rb:6:in `process'
        from post-autoconf.rb:16:in `<main>'
```

aclocal は、Automake のためのマクロらしい

- http://stackoverflow.com/questions/18813967/compiling-hadoop-examples-in-mac
- http://www.fireproject.jp/feature/automake/basic/autotools.html

これもインストール

```
$ brew install automake
```

### rb++の制限の為に、include/test.h を適当なnamespaceでラップする

```include/test.h
namespace MyLib {
  class Test{
    public:
      Test();
      ~Test();
      void sample_method();
  };
}
```

### export.rb を設定

rb++ で生成するラッパーオブジェクトが何を参照するのかを、Extension クラスに設定する

- 第1引数 : 読み込むヘッダ
- include_paths : インクルードファイルのパス。 gcc の -I オプションに追加される
- library_paths : ライブラリのパス。 gcc の -l オプションに追加される
- include_source_dir : ソースコードのディレクトリ

各ファイル、ディレクトリの path は、絶対 path を指定しないとエラーになる

```export.rb
require 'rubygems'
require 'rbplusplus'
include RbPlusPlus

Extension.new 'test' do |e|
  pwd = File.expand_path(File.dirname(__FILE__))
  e.sources(
    File.join(pwd, 'ext', 'include', 'test.h'),
    :include_paths      => File.join(pwd, 'ext', 'include'),
    :include_source_dir => File.join(pwd, 'ext', 'src')
  )
  e.namespace 'MyLib'
end
```

### export.rb の実行

```
$ bundle exec ruby ./export.rb
```

### gccxml が無いと怒られる

```
vendor/gems/ruby/1.9.1/gems/gccxml_gem-0.9.3/gccxml.rb:55:in `find_exe': Unable to find gccxml executable locally or on your PATH. (RuntimeError)
```

gccxml は、c++の静的解析ツール

## gccxmlのインストール

gccxml_gem の中の rake を使って、gccxml をインストールする必要があるっぽい

- ちなみに、gccxml_gem の中の人は、rbgccxml というのを別に作っていて、そっちを使えば楽にインストールできるよ! みたいなことをいっている → [rbgccxml | RubyGems.org | your community gem host](https://rubygems.org/gems/rbgccxml)
- rb++ の中の人も同一人物なので、rb++ も、rbgccxml を使うようにして欲しい...


ext/gccxml の中に、gccxml のソースコードを用意する

```
$ cd vendor/gems/ruby/1.9.1/gems/gccxml_gem-0.9.3
$ mkdir -p ext
$ cd ext
$ wget https://github.com/gccxml/gccxml/archive/master.tar.gz
```

取得した、gz の中身を、gccxml_gem の Rake が求める感じに修正

```
$ tar -zxvf master.tar.gz
$ mv gccxml-master gccxml
$ tar -zcvf gccxml.tar.gz gccxml
$ rm -rf gccxml/
$ cd ..
```

インストール

```
$ bundle exec rake build_gccxml
```

### cmake が無いと言われる

インストールする

```
$ brew install cmake
```


### もう一度インストール

```
$ bundle exec rake build_gccxml
```

バイナリファイルを binstubs にリンクする

```
$ ln -s vendor/gems/ruby/1.9.1/gems/gccxml_gem-0.9.3/bin/* vendor/bin/.
```

## 再度 export.rb の実行

```
$ bundle exec ruby ./export.rb
```

それらしいものが generated に自動生成される

```
$ tree generated/
generated/
├── Makefile
├── _MyLib_Test.rb.cpp
├── _MyLib_Test.rb.hpp
├── _MyLib_Test.rb.o
├── extconf.rb
├── rbpp_compile.log
├── test.bundle
├── test.cpp
├── test.o
├── test.rb.cpp
└── test.rb.o
```


```generated/_MyLib_Test.rb.cpp
#include <rice/Data_Type.hpp>
#include <rice/Constructor.hpp>
#include <rice/Class.hpp>
#include "./include/test.h"


#include "_MyLib_Test.rb.hpp"

void register_MyLib_Test() {
	Rice::Data_Type< MyLib::Test > rb_cTest =  Rice::define_class< MyLib::Test >("Test");
	rb_cTest.define_constructor(Rice::Constructor< MyLib::Test >());

	{
		typedef void ( MyLib::Test::*sample_method_func_type )(  );
		rb_cTest.define_method("sample_method", sample_method_func_type( &MyLib::Test::sample_method ));
	}
}
```

## 生成されたライブラリのコンパイル

```
$ cd generated
$ make clean
$ make
```

## rubyから呼び出してみる

```
$ ruby -e "require './generated/test'; Test.new.sample_method"
call constructor
call sample_method
calll destructor
```

できたー!!

## ソースコード

- https://github.com/kasei-san/c--_ruby_extention_sample

## 参考

- [第３部 ツールを使って自動で処理する - tuedaの日記](http://d.hatena.ne.jp/tueda_wolf/20091205/p4)
- [第18章 ロード](http://i.loveruby.net/ja/rhg/book/load.html)

## gccxml を bundler ではなく、/usr/local/bin にインストールする場合

```
$ wget https://github.com/gccxml/gccxml/archive/master.tar.gz
$ tar -zxvf master.tar.gz
$ cmake gccxml-master
$ make 
$ make install
```

## 疑問点

### Extension で絶対 path しか指定できないと、公開するとき不便じゃない?

やり方は2つあると思う

#### generated/ をリポジトリに含まずに、各々の環境で generate して貰う方法

利点

- 生成されたコードをいじる必要が無い

欠点

- 各自が rb++ を動かすための、いろいろめんどくさい環境を用意する必要がある
- gem などのライブラリとして配布しづらい

#### extconf.rb の中の CPPFLAGS を相対 path に書き換える方法


利点

- 各自が rb++ を動かすための環境を用意する必要がない
- gem などのライブラリとして配布しやすい

欠点(というか疑問)

- リポジトリにどういう風にファイルを置くの?
- rb++で生成するためのリポジトリを別に用意して、そこ経由で配布用のブランチを別に作るとか???

どういう風にするのが標準的なのだろ?