LoginSignup
26
26

More than 5 years have passed since last update.

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

Last updated at Posted at 2014-04-13

まずは、適当な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++

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 のためのマクロらしい

これもインストール

$ 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++で生成するためのリポジトリを別に用意して、そこ経由で配布用のブランチを別に作るとか???

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

26
26
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
26