- ruby_options が値を返すようになっている
- ruby_run という関数がない
など、Rubyの初期化・実行に関する README.EXT の内容は現状に追いついていないようです。
そのようなわけで、Ruby 1.9 を外部から利用する方法を少し調べてみました。
- アプリケーションに Ruby を組み込みたい
- rubyw.exe に代わる前処理プログラムを作りたい
といったときに役立つかもしれません。
利用したのは Ruby 1.9.0-3です。 これを VC++ 2008 でビルドした msvcr90-ruby190.dll を C プログラムから利用してみます。
こちらがアプリケーションのソースコード。必要最小限のエントリーポイント を取得した後、インタプリタの初期化と実行に移っています。
UseRubyDLL.c:
#define WIN32_LEAN_AND_MEAN #include#define APP_NAME "UseRubyDLL.exe" #define RUBY_DLL_NAME "d:\\ruby19\\bin\\msvcr90-ruby190.dll" #define RUBY_SCRIPT_NAME "script.rb" HINSTANCE ruby_dll; /* from include/ruby/ruby.h */ void (*ruby_init)( void ); void (*ruby_sysinit)( int*, char*** ); void* (*ruby_options)( int, char** ); /* from include/ruby/intern.h */ void (*ruby_init_loadpath)( void ); int (*ruby_run_node)( void* ); void WinMainCRTStartup() { /* Dummy arguments for ruby_sysinit(). */ int sysinit_argc = 0; char** sysinit_argv = NULL; /* Manually construct interpreter arguments. */ int app_argc = 3; char* app_args[3]; char** app_argv = app_args; app_args[0] = APP_NAME; app_args[1] = RUBY_SCRIPT_NAME; app_args[2] = "See ya."; ruby_dll = LoadLibrary( RUBY_DLL_NAME ); if ( ruby_dll ) { ruby_sysinit = (void (*)( int*, char*** ))GetProcAddress( ruby_dll, "ruby_sysinit" ); ruby_init = (void (*)( void ))GetProcAddress( ruby_dll, "ruby_init" ); ruby_init_loadpath = (void (*)( void ))GetProcAddress( ruby_dll, "ruby_init_loadpath" ); ruby_options = (void* (*)( int, char** ))GetProcAddress( ruby_dll, "ruby_options" ); ruby_run_node = (int (*)( void* ))GetProcAddress( ruby_dll, "ruby_run_node" ); ruby_sysinit( &sysinit_argc, &sysinit_argv ); ruby_init(); ruby_init_loadpath(); ruby_run_node( ruby_options( app_argc, app_argv ) ); /* NOTE: After the execution, +ruby_run_node+ automatically calls +ruby_cleanup+. So there's no need to explicitly call Ruby's finalizer here(otherwise cause disaster). */ FreeLibrary( ruby_dll ); } }
上記の ruby_*() 系関数の利用方法は Ruby のソースコード(winmain.c, eval.c, etc.)から把握したものです。 当然ですがインタプリタ DLL の場所など、ハードコードした部分は適宜変更する必要があります。
アプリケーションから呼び出すスクリプトはこちら。インタプリタ DLL の場所 次第では、標準ライブラリ(ここでは matrix.rb)jの require に失敗しますの でご注意を。
script.rb:
require 'matrix' File.open( 'result.txt', 'w' ) do |f| f.puts Time.now f.puts Vector[rand,100*rand,10000*rand] f << ARGV end
ビルドによって生成された UseRubyDLL.exe を以下のように実行します。 script.rb は同じ場所にあるものとします。
$ ./UseRubyDLL
実行すると同じ場所に result.txt ができているはず。以下が結果の一例です。
result.txt(例)
2008-08-03 18:52:16 +0900 Vector[0.434646515113465, 27.44515575993, 9534.51425972162] ["See ya."]
ちなみにビルド用の Makefile はこちら。実行ファイルを小さくするため、あえて Cygwin gcc (gcc -mno-cygwin -mwindows) を利用してみました。
Makefile:
TARGET = UseRubyDLL.exe SRC = $(TARGET:exe=c) OBJ = $(TARGET:exe=o) CC = gcc -mno-cygwin -mwindows CFLAGS = -Wall -Os LDFLAGS = -lkernel32 -nostdlib all: $(TARGET) $(TARGET): $(OBJ) $(CC) -o $(TARGET) $< $(LDFLAGS) strip $(TARGET) clean: rm $(OBJ) $(TARGET) .c.o: $(CC) $(CFLAGS) -c $<
手元での結果はこちら:
$ gcc --version gcc (GCC) 3.4.4 (cygming special, gdc 0.12, using dmd 0.125) Copyright (C) 2004 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. kazuwe@kazuwe-PC ~/UseRubyDLL $ ls -l UseRubyDLL.exe -rwxr-xr-x 1 kazuwe None 2560 Aug 3 18:58 UseRubyDLL.exe
ちょっと心配になるくらい小さくなるものですね :-)
参考
- embedding Ruby 1.9.0 inside pthread
- BDS2006(C++Builder)からRubyを使う (山本隆の開発日誌)