作者: 楊粼波
Email: tx7do#yahoo.com.cn 突發奇想的想要把Ruby嵌入到C++的程序里面去,想了于是就去做了.現在只在Windows下面測試通過,其他系統下,我還沒有試過,不過基本過程大差不差的.
需要準備些什么? 1.Ruby(
http://www.ruby-lang.org/en/downloads/),去頁面中下載最新版本的Ruby 的安裝包;
2.Swig(
http://www.swig.org/download.html),去頁面中下載最新的安裝包.
什么是Ruby? Ruby,一種為簡單快捷面向對象編程(面向對象程序設計)而創的腳本語言,由日本人松本行弘(まつもとゆきひろ,英譯:Yukihiro Matsumoto,外號matz)開發,遵守GPL協議和Ruby License。Ruby的作者認為Ruby > (Smalltalk + Perl) / 2,表示Ruby是一個語法像Smalltalk一樣完全面向對象、腳本執行、又有Perl強大的文字處理功能的編程語言。
什么是SWIG? SWIG(Simplified Wrapper and Interface Generator)是個幫助使用C或者C++編寫的軟件能與其它各種高級編程語言進行嵌入聯接的開發工具。SWIG能應用于各種不同類型的語言包括常用腳本編譯語言例如Perl, PHP, Python, Tcl, Ruby and PHP。
簡單來說,主要用于導出C/C++程序庫給腳本語言使用的一個自動化工具.導出的工作是非常機械化,而且繁復的.
編譯環境設置 Ruby在Windows下:
頭文件在$RUBY_HOME/lib/ruby/1.8/i386-mswin32;
lib在$RUBY_HOME/lib,為msvcrt-ruby18.lib;
dll在RUBY_HOME/bin,其實只有一個dll,就是:msvcrt-ruby18.dll.
在這里需要注意到的是,$RUBY_HOME/lib/ruby/1.8/i386-mswin32/config.h這個文件對VC的版本做了限制:
#if _MSC_VER != 1200
#error MSC version unmatch
#endif
所以,如果VC不是這個版本的話,編譯是通不過的,對此問題,最簡單的辦法就是:將這三行代碼注釋掉,就可以了.
C++解釋器包裹代碼
頭文件

#ifndef __RubyInterpreter_H__
#define __RubyInterpreter_H__


#include <string>

typedef unsigned long VALUE;
typedef std::string String;

typedef VALUE(*staticValueMethod)(
);
typedef VALUE(*ProtectedMethod)(VALUE);

class RubyInterpreter


{
public:
RubyInterpreter();
virtual ~RubyInterpreter();

public:

/**//// 初始化解釋器
void initializeInterpreter();


/**//// 終止解釋器
void finalizeInterpreter();


/**//// 設置
void setOutputFunction(staticValueMethod func);


/**//// 加入引用庫的搜索路徑
void addSearchPath(const String& path);

public:

/**//// 執行語句
bool execute(const String& command);

/**//// 執行文件
bool executeFile(String rubyfile);

private:

/**//// 記錄錯誤日志
void logRubyErrors(const std::string& intro, int errorcode);

/**////
void loadProtected(ProtectedMethod func, VALUE args,
const std::string& msg, bool exitOnFail = false);


/**////
static VALUE loadDlls(VALUE);
};


#endif

源文件

#include "StdAfx.h"
#include "RubyInterpreter.h"

#include "FixRubyHeaders.h"
#include <ruby.h>
#include "FixRubyHeaders.h"


RubyInterpreter::RubyInterpreter()


{

}

RubyInterpreter::~RubyInterpreter()


{

}

void RubyInterpreter::initializeInterpreter()


{
#if defined(NT)
static int dummyargc(0);
static char** vec;
NtInitialize(&dummyargc, &vec);
#endif

// 初始化Ruby
ruby_init();

// 使用UTF8編碼
execute( "$KCODE = 'u'" );

// addSearchPath();

// 初始化腳本加載路徑
ruby_init_loadpath();

// 設置安全級別
rb_set_safe_level(0);

//
ruby_script("ruby");

//loadProtected(&RubyInterpreter::loadDlls, 0, "Ruby error while loading dlls");
}

void RubyInterpreter::finalizeInterpreter()


{
ruby_finalize();
}

void RubyInterpreter::setOutputFunction(staticValueMethod func)


{
rb_defout = rb_str_new("", 0);

// 定義一個虛擬類的方法
rb_define_singleton_method(rb_defout, "write", func, 1);
}

void RubyInterpreter::addSearchPath(const String& path)


{
ruby_incpush(path.c_str());
}

VALUE RubyInterpreter::loadDlls(VALUE val)


{
String lib;

//
return rb_require(lib.c_str());
}

void RubyInterpreter::loadProtected(ProtectedMethod func,
VALUE val,
const std::string& msg,
bool exitOnFail)


{
int error = 0;
rb_protect(func, val, &error);
logRubyErrors("Ruby error while initializing", error);
}

void RubyInterpreter::logRubyErrors(const std::string& intro, int errorcode)


{
if (errorcode != 0)

{
VALUE info = rb_inspect(ruby_errinfo);
rb_backtrace();
if (intro.length() > 0)

{
}
}
}

bool RubyInterpreter::execute(const String& command)


{
int status = -1;

rb_eval_string_protect(command.c_str(), &status);

logRubyErrors("", status);

if ( status )

{
rb_eval_string_protect("print $!", &status);
return false;
}

return true;
}

bool RubyInterpreter::executeFile(String rubyfile)


{
bool error = execute("load '" + rubyfile + "'");
return error;
}

SWIG的使用
步驟大致為:
1. 編寫后綴為.i的腳本;
2. 使用swig生成導出代碼,假如腳本名為:sample.i,那么生成的源碼文件名規則就為:sample_wrap.cpp/.c.
3. 將生成的cpp加入動態鏈接庫,然后編譯.
最簡單的.i腳本為:
%module Export4ScriptLib

%
{
#include "Player.h"
%}


%include "stl.i"
%include "Player.h"
Edit:如果想要使用STL的導出類,那就需要添加%include "stl.i"
假如說,頭文件里面定義的所有的類,類所有的方法,你都要將之導出,那么以上就足夠了.但是,假如你只需要導出部分的類,部分的類的方法.那么你就需要自己手動寫入到.i腳本里面去了.
生成代碼的命令為:
swig.exe -c++ -ruby Exports.i
這樣寫的前提是你已經吧swig的路徑加入到環境變量里面去了,其中第一個參數表示的是導出的代碼為c++,第二個參數表示的目標腳本語言是誰,第三個參數是.i腳本的路徑名.我寫了一個批處理:invoke_swig.bat,做這件事情.不過更完美的做法是在VC項目里面的"預生成事件"加入此語句.
剩下的事情就是把生成的代碼和要導出的代碼編譯一邊,就可以開始使用導出的C++庫了.
測試在實例代碼里面:Export4ScriptLib工程是動態鏈接庫工程,testRubyInterpreter是測試用的可執行程序工程.
測試用的Ruby代碼test.rb如下:
require 'Export4ScriptLib'

print "hello 你好!\n"


ply = Export4ScriptLib::Player.new
ply.Jump();
ply.Move(100, 2000);
測試用C++代碼如下:
class testClient
{
public:
testClient()
{
mRubyInterpreter = new RubyInterpreter();
mRubyInterpreter->initializeInterpreter();
}
~testClient()
{
delete mRubyInterpreter;
}

void exec()
{
// 執行語句
mRubyInterpreter->execute("print \"This is C++ call Ruby print funtion!\n\"");
// 執行文件
mRubyInterpreter->executeFile("test.rb");
}

private:
RubyInterpreter* mRubyInterpreter;
};
源代碼下載testRubyInterpreter.rar參考資料1. 什么是Ruby
http://www.kuqin.com/beginner/ruby.html
2. SWIG
http://swig.minidx.com/ 3.
http://raylinn.javaeye.com/blog/629329