まずは、適当な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++って?
ruby から C++ のクラス を拡張ライブラリとして呼び出す場合、結構面倒なラッパーを用意して、C++ のクラスと ruby の橋渡しをしてあげないといけない
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)として外に出すオプション
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
- 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
できたー!!
ソースコード
参考
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++で生成するためのリポジトリを別に用意して、そこ経由で配布用のブランチを別に作るとか???
どういう風にするのが標準的なのだろ?