Top Page > C++ BuilderでRubyの拡張モジュールを作る


C++ BuilderでRubyの拡張モジュールを作る


○一般的な話

Rubyの機能を拡張するためにC/C++などで書かれたライブラリを拡張モジュールと言います。Windowsでは、その実体はDLLです。

拡張モジュールの書き方は、Rubyの doc/ruby/ruby-1.6.7/README.EXT.ja に詳しく書かれています。Ruby/KAKASI拡張モジュールのページには、拡張モジュールのサンプルコードと解説が有って、役立ちます。

○はじめに

BCB6 Proを例に説明します。以下の説明は、「こうやったらうまくいったっぽい」というだけで、正しいやり方なのかは分かりません。何か間違ってたり、もっと簡単な方法が有ったら指摘してください。

○BCBでDLLのプロジェクトを作る

普通に作ります。念のため説明すると、

○ruby.h をインクルード

ruby.h をインクルードします。 ruby.h と関連ヘッダファイルはRubyの lib/ruby/1.6/i586-mswin32 に有ります。

mswin32-ruby16.lib をプロジェクトに追加

mswin32-ruby16.lib をプロジェクトに追加する必要が有ります。mingw版バイナリでは lib ディレクトリに入ってますが、これは(多分)VC用なので、このままでは使えません。コマンドプロンプトで

coff2omf mswin32-ruby16.lib mswin32-ruby16-bcb.lib

としてBCC版Libファイルを作り、これをプロジェクトに追加しましょう。ついでに、Rubyの bin ディレクトリの mswin32-ruby16.dll をSystemディレクトリにコピーしておきましょう。

○必要な関数を定義する

Ruby/KAKASI拡張モジュールなどを見れば分かるように、 Init_dlltest (dlltest の所はプロジェクト名)などの関数を定義します。C++で書いてる場合は、

extern "C"{
 void Init_dlltest();
}

のように、関数宣言を extern "C"{ … } で囲みます。関数定義を囲む必要は無いです。この辺の関数の書き方について詳しくは README.EXT.ja を読んでください。

○関数をエクスポートする

普通なら、関数に __export や __declspec(dllexport) を付ければエクスポート完了!なんですが、これだと例えば _Init_dlltest のように、エクスポートされた関数名の先頭にアンダスコアが付いちゃうんですね。で、これだとRubyがうまく呼び出せないようです。そこで「モジュール定義ファイル」ってのを作ります。

CODEPRELOAD MOVEABLE DISCARDABLE
DATAPRELOAD MOVEABLE SINGLE
HEAPSIZE4096
STACKSIZE1048576
EXPORTS
  Init_dlltest=_Init_dlltest

こんな風に書いたものを、 dlltest.def として dlltest.bpr と同じディレクトリに保存します。最後の行が、「内部名 _Init_dlltest の関数を Init_dlltest という名前でエクスポート」と言う意味な訳ですね。で、[プロジェクト]-[オプションソースの編集]で

<DEFFILE value=""/>

という所を

<DEFFILE value="dlltest.def"/>

に書き換えます。これでメイクすれば、 Init_dlltest という関数がエクスポートされた dlltest.dll ができます。 eXeScope などのツールで確認できます。ちなみに、 rb_define_module_function などで登録した関数をエクスポートする必要は無いようです。

ここで1つ注意点。BCB6のバグなのか、オプションソースのDEFFILEの設定は、プロジェクトを開きなおすとクリアされてしまいます。メイクする前に一旦 dlltest.bpr.xml のタブを閉じて開きなおし、DEFFILEの設定がクリアされていれば書き直しましょう。*1

○Rubyから使う

Rubyファイルで

require 'dlltest'

とすれば、以後、 dlltest.dll で定義したモジュールやクラスが使えます。 dlltest.dll はRubyファイルと同じディレクトリか、Systemディレクトリ辺りに置きましょう。

Rubyの require '***' とDLLの Init_*** 関数で、***の部分(ここでは"dlltest")の大・小文字が違うと失敗します。小文字で揃えるのが無難そうです。

○サンプル

--- dlltest_main.cpp ---

#include <windows.h>
#pragma hdrstop
#include <iostream>
#include <ruby.h>
using namespace std;
 
extern "C"{
 void Init_dlltest();
 VALUE rb_DllTest_hello(VALUE obj);
}
 
void Init_dlltest()
{
 VALUE module= rb_define_module("DllTest");
 rb_define_module_function(module, "hello", (VALUE (*)(...))rb_DllTest_hello, 0);
}
 
VALUE rb_DllTest_hello(VALUE obj)
{
 cout << "Hello world." << endl;
 return INT2NUM(0);
}
 
#pragma argsused
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
 return 1;
}

--- test.rb ---

require 'dlltest'
DllTest.hello()


*1 毎回DEFFILEを書き直すのがうざい方は、[プロジェクト]-[メイクファイルの作成]でメイクファイル(例:dlltest.mak)を作成しましょう。コマンドプロンプトで
make -fdlltest.mak
とすると、統合環境を使わずにDLLをメイクできます。ただしこの場合、ファイルの追加などのためには手動でメイクファイルをいじる必要が有ります。


Gimite 市川 <gimite@mx12.freecom.ne.jp>


C++ BuilderでRubyの拡張モジュールを作る < Top Page