SyntaxHighlighter

ラベル Ruby の投稿を表示しています。 すべての投稿を表示
ラベル Ruby の投稿を表示しています。 すべての投稿を表示

2017年1月21日土曜日

Ruby で net/http を使ってファイルなどを multipart で POST する

Ruby で net/http を使ってファイルなどを multipart で POST する

今回は、自分用の覚え書きです。

net/http を用いた multipart の POST

Ruby で HTTP クライアントを書いていると、しばしばファイルなどを POST したいことがあります。
この場合、 multipart なデータを POST することになります。

この機能は、標準ライブラリである net/http のみを用いて、割と簡単に実現できます。

よくネットで見つけられる set_form_data ではなく、set_form を使うのがポイントです。

set_form(params, enctype="application/x-www-form-urlencoded", formopt={}) の引数

params

set_form の一つ目の引数である params には、配列の配列を渡します。
それぞれの配列は、以下の形式になります。

[ (パラメータ名), (値 or ファイルオブジェクト), (オプションのハッシュ) ]

上の使用例を見れば簡単に理解できると思いますが、オプションのハッシュを指定することで、ファイル名や種類を指定することができます。

これらの値の組を配列として指定することで、送信するデータを決められます。

enctype

二つ目の引数である enctype には、 "multipart/form-data" を指定します。

デフォルトは "application/x-www-form-urlencoded" なので、ファイル送信するときは、必ず指定する必要があります。

formopt

三つ目の引数である formopt には、 :charset や :boundary を指定することができます。

:boundary は、指定しなければランダムで URL-safe な文字列が生成されて使われます。
そのため、通常は特に指定する必要はないでしょう。

以上のような引数を指定し、 set_form を使うことで、簡単にファイルなどを multipart で送信することができます。

なぜか日本語のリファレンスから漏れており、(そのせいか) 日本語の記事があまりないようなので、メモとして書いておきます。
暇があったらリファレンスに載せてもらうようにお願いする予定です。

2013年12月15日日曜日

SevenZipRuby 作成メモ 2 - C++ での Ruby 拡張ライブラリのコーディング

SevenZipRuby 作成メモ 2 - C++ での Ruby 拡張ライブラリのコーディング

7z ファイルを読み書きする Ruby gem ライブラリである seven_zip_ruby も、無事に rubygems で公開しました。
ダウンロード数から察するに、あまり必要とされていないような気がしないでもないですが、以下にリンクをはっておきます。

今回は、 SevenZipRuby の作成メモ 2 として、 C++ での Ruby 拡張ライブラリのコーディングについて書いておきます。

概要

C++ での Ruby 拡張ライブラリのコーディングについては、 tueda の日記 - (Cではなく)C++でRubyの拡張ライブラリを作るには などが非常に分かりやすく書かれているので、とても参考になると思います。
また、 seven_zip_ruby では使いませんでしたが、 C++ で Ruby 拡張ライブラリを作成するための Rice というライブラリも公開されており、 Rice を使用して Ruby の拡張機能を C++ で作成するなどのページが参考になると思います。

今回は、私が個人的に気をつける必要があると感じた、例外まわりの実装などについて書いておこうと思います。

Ruby の例外機構

Ruby では、 raise "hoge" や raise SyntaxError, "invalid syntax" のように例外を発生させ、それを外側のメソッドなどで begin, rescue, ensure を使って捕捉することができます。

この仕組みは、 MRI (CRuby) の場合、 setjmp と longjmp によって実現されています。

setjmp と longjmp

これらは C 言語で大域脱出を実現するための機構であり、例えば以下のようなことができます。

上記の例のように、 setjmp が呼ばれると、将来その関数の先で longjmp が呼ばれたときのために、「戻り先」を示すための CPU 依存の情報 (実行コンテキスト) を引数 gBuf に保存します。
その後、 longjmp が呼ばれると、引数で渡された実行コンテキストから、 setjmp を呼んだときの「戻り先」を求め、その状態に戻ります。

とてもとても大雑把に書くと、ネストされた関数内から外側の関数へ一度に移動できる goto のようなものです。
Java にはラベル付き break がありますが、「関数をまたいで使えるラベル付き break 」のようなイメージです。

より詳細な (そして正確な) 説明としては longjmpと例外などがとても分かりやすいです。

しかし、 goto などと異なり、 setjmp, longjmp は C++ で使う際には「デストラクタとの併用が困難」という大きな注意点があります。

C++ と setjmp, longjmp

例えば以下のようなコードを考えます。

この場合、 Constructor, Destructor, Error! と表示され、デストラクタ ~Test() が呼ばれていることが分かります。

一方で、以下のようなコードの場合、デストラクタは呼ばれません。

このように、 longjmp は強制的に setjmp を呼んだときのスタック状態に戻すので、 setjmp, longjmp の間で呼ばれるべきデストラクタは一切呼ばれません。
ここはとても重要なポイントです。

例外を考慮した C++ による Ruby の拡張ライブラリの作成

既に書いたように、 Ruby の例外機構は setjmp, longjmp を使って実現されています。
大雑把に書くと、 Ruby コード中の begin の部分で setjmp され、 raise Hoge の部分で longjmp されています。

そのため、 C++ で Ruby の拡張ライブラリを書く場合、前述したようなデストラクタの呼び出しがされない場合があることに注意が必要です。
例えば、 std::vector なんかを使っていると、簡単にメモリーリークが発生します。

C++ で書く場合は、こういったことを考慮して書く必要があります。
例えば、以下のように書く必要があります。

上記のように書けば、 Ruby の例外による longjmp の範囲を rb_protect で呼び出した sub_function の内部に閉じ込めることができます。

でも、それぞれの Ruby メソッドの呼び出しについて関数を分けるのは面倒です。

ラムダ関数の活用

なので、 C++ のラムダ関数を使ってみることにします。

例えば、以下のような関数を作っておいて、 rb_protect に渡すようにします。

こんな感じのテンプレート関数を作っておくと、あとはラムダ関数を使うことで、割と楽に Ruby の関数を呼び出すことができます。

C 言語と C++ の関数呼び出し規約におけるコンパイラ依存性

厳密には、上記の方法はコンパイラに依存しており、うまく動かないこともあります。

run_protect 関数の中で、 C 言語の関数である rb_protect を呼んでおり、その第一引数として run_functor_for_protect<T> のように C++ の関数ポインタを渡しています。

この部分がコンパイラに依存しており、うまく動かないかもしれない部分です。たぶん。

本来であれば、 C 言語の関数に関数ポインタを渡す場合は、 extern "C" 宣言された関数でなければなりません。

上の例のコードがうまく動くのは、「C 言語と C++ の関数の呼び出し規約が一致しているとき」です。
例えば「C 言語の関数の呼び出し規約は cdecl なのに、 C++ の関数の呼び出し規約は stdcall」みたいなコンパイラがあると、当然うまく動作しません。ただし、そんなコンパイラがあるのかは知りません。

C++ の仕様としては、こういう場合は extern "C" を付けることで、呼び出し規約 (と関数の名前修飾) が C 言語と同じになることが保証されていますが、テンプレート関数には extern "C" を付けることはできません。
名前修飾はともかくとして、呼び出し規約だけは C 言語と同じになるような書き方をしたいのですが、標準の書き方ではなさそうな感じです。

function クラスの利用

そのため、ラムダ関数とともに C++11 で追加された function クラスを使うことにします。

今回の場合では以下のようにすると、コンパイラ依存なコードにならないでしょう。

以上のように、 C++ で Ruby の拡張ライブラリを書くのは、割と面倒です。
seven_zip_ruby のように、呼び出し先の DLL が生の C++ を必要とするような環境でもない限り、 C で書くか、 Rice などのライブラリを使うのが楽でしょう。

2013/12/23 function に関する記述を追加

2013年11月5日火曜日

SevenZipRuby 作成メモ 1 - 7z.dll の概要と、 7z.dll と Ruby の橋渡し

SevenZipRuby 作成メモ 1 - 7z.dll の概要と、 7z.dll と Ruby の橋渡し

これから何回か、 SevenZipRuby を作成する際に悩んだことなどをメモしていこうと思います。

7z.dll のバージョンは 9.20 に基づいていますが、 7z.dll の作者の Igor さんによると、 9.30 でもこのインターフェースは使えるそうです。

なお、念のために断っておきますが、「7z.exe を呼んだらいいんじゃない?」というのはごもっともなのですが、 Ruby 拡張機能を作ること自体が目的なので、いろいろ面倒なことをしています。(DLL を呼ばないとできないこともありますし)


今回は、 7z.dll の概要と、 7z.dll と Ruby のバインディング部分を書くにあたって注意しなければならないことについて書いておきます。
結論としては、 7z.dll を呼ぶ以上、下記の二点を考慮しなければならず、面倒だ、という話です。

  • Ruby の例外機構 (setjmp, longjmp) を考慮した C++ のコーディング
  • 7z.dll が裏で生成する別スレッドを考慮した Ruby のメソッド呼び出し

7z.dll の仕様

今回の gem ライブラリでは、 7z.dll を内部的に呼ぼうと思ったので、 7z.dll の仕様を調べる必要がありました。

7z.dll の仕様は、断片的な情報しか見つかりませんでしたが、 7-Zip の FAQ の How can I add support for 7z archives to my application? に書かれているように、 7-Zip ソースコード中の Client7z.cpp を見るのが楽そうです。

以下では、私が SevenZipRuby を実装する際に調べたことをまとめておきます。
メモ書きだったものを、文体だけ変更して載せているので、あまり読みやすくないと思いますが、何かの参考になればと思います。

7zip アーカイブの展開の流れ

7zip アーカイブを 7z.dll を用いて展開する場合は、以下のような流れになります。

  1. 7z.dll から CreateObject 関数のポインタを取得する。
  2. CreateObject 関数で、 7zip アーカイブの展開用インターフェースである IInArchive インターフェースへのポインタを取得する。
  3. IInArchive でデータを読み込むために、下記のインターフェースの派生クラスを用意する。
    IInStream
    読み込み対象のファイルにアクセスするインターフェース
    IOutStream
    アーカイブ内のデータを展開する際の書き込み先のファイルにアクセスするインターフェース
    IArchiveOpenCallback
    IInArchive の Open 関数を呼び出す際に必要なインターフェース
    IArchiveExtractCallback
    IInArchive の Extract 関数を呼び出す際に必要なインターフェース
  4. これらのインターフェースの派生クラスのインスタンスを作成し、 IInArchive の Open, Extract 関数を呼び出す。

CreateObject

7z.dll では、種々のアーカイブをサポートしており、それらを扱うクラスは、 IInArchive もしくは IOutArchive クラスの派生クラスとしてそれぞれ定義されています。
7z.dll でアーカイブを扱う場合、そのアーカイブの種類に合った派生クラスのインスタンスを、 7z.dll がエクスポートしている CreateObject 関数を通じて取得する必要があります。そのため、まずは CreateObject 関数を DLL から取得する必要があります。
CreateObject 関数自体は、 DLL からエクスポートされているので、以下のように取得できます。

IInArchive インターフェースの取得

続いて、取得した CreateObject から、 7zip アーカイブを展開するためのインターフェースを取得します。

CreateObject 関数の第一引数には、 CLSID_CFormat7z や CLSID_CFormatZip などのような、アーカイブのファイルフォーマットを示す GUID を渡します。
一覧は CPP/7zip/Guid.txt にまとまっているので、見るとよいでしょう。 例えば CLSID_CFormat7z であれば、 {23170F69-40C1-278A-1000-000110010000} になります。

第二引数には、 IID_IInArchive か IID_IOutArchive を指定します。
今回は、展開用のインターフェースが欲しいので、 IID_IInArchive を指定します。
こちらも、GUID の値は CPP/7zip/Guid.txt に載っています。 IID_IInArchive であれば、 {23170F69-40C1-278A-0000-000600600000} です。

あとは、これで得られた archive ポインタを通じて、好きな処理をしていくことになります。
なお、 IInArchive の定義は、 CPP/7zip/Archive/IArchive.h の INTERFACE_IInArchive の部分を見ると分かります。 関数の名前から、だいたい意味は分かるのではないかと思います。

IInArchive でアーカイブを読み込むために必要な諸クラス

IInStream, IOutStream, IArchiveOpenCallback, IArchiveExtractCallback の派生クラスを、すべて定義しておく必要があります。

ここでは、サンプルとして IInStream の派生クラスの定義について記述します。

IInStream のメンバの定義

IInStream は、ファイルの読み込みを抽象化したインターフェースであり、以下の関数を持っています (Read は親クラスの ISequentialInStream のメンバーです) 。
なお、以下の記述は WINAPI などの呼び出し規約を書いていないので、そのままでは使えません。実際の定義では、 STDMETHOD マクロが使われています。

このインターフェースを継承したクラスを独自に作成することで、ファイルから読み込ませることや、ネットワークソケットから読み込ませることなどが自由にできます。

Ruby との橋渡し

7z.dll と Ruby のバインディングを行うには、 7z.dll が必要とする IInStream などのインターフェースを継承したクラスを作成し、そのクラスで適切に Ruby のメソッドを呼んでやることがメインとなります。

エラー処理などを省くと、イメージとしては下記のようになります。

このようにすることで、 7z.dll の世界と Ruby の世界を結ぶことができます。

しかし、上記のようなコードは期待した通りに動きません。
これは、大きくは以下の二点の理由によります。

  • Ruby の例外に対し、安全でない。
  • IInStream の Read, Seek が、別スレッドで呼ばれることがある。

Ruby (MRI) は C で実装されており、 Ruby の例外などの実装には setjmp, longjmp を使っています。
この関数によるジャンプは、 C++ のデストラクタ呼び出しを保証しないので、混在させて使うことができません。
例えば、 RubyInStream::Read の中で Ruby の例外が発生すると、 Read を呼び出した関数で定義されたローカル変数のデストラクタなどは、呼ばれないままにスタックの巻き戻しが発生します。これは容易に 7z.dll のクラッシュを発生させます。

二点目については、 7z.dll と Ruby の実装が深く関わっています。
7z.dll はマルチスレッドで動作するように設計されており、 Read は複数のスレッドから呼ばれることがあります。
7z.dll 側で同期をとった上で呼ばれているので、同時に複数のスレッドから Read が呼ばれることはないのですが、 Ruby の実装の制約上、 GVL という Mutex を取得していないスレッドが、 Ruby の関数を呼ぶことは禁止されています。
そのため、 7z.dll が生成した別スレッドから Read が呼ばれると、そのスレッドは GVL Mutex を取得していないので、 Ruby の関数を呼んではいけません。もし呼んでしまうと、 Ruby が Segmentation Fault で死ぬことになります。

というわけで、 7z.dll を Ruby から使えるようにするためには、以下の二点を考慮した設計にしなければなりません。

  • Ruby の例外機構 (setjmp, longjmp) を考慮した C++ のコーディング
  • 7z.dll が裏で生成する別スレッドを考慮した Ruby のメソッド呼び出し

なんて面倒なんだ。

2013年10月27日日曜日

SevenZipRuby - Ruby 用 7zip gem ライブラリ

SevenZipRuby - Ruby 用 7zip gem ライブラリ

Ruby の C 拡張機能や、 gem ライブラリ作成の勉強をしようと思ったので、 7-Zip のアーカイブを読み書きする拡張ライブラリを作成しました。
まだ開発中で、どこまで作り込むかは不明ですが、とりあえず GitHub に公開しました。
seven_zip_ruby にあるので、興味がありましたらご覧ください。

なお、 API の仕様などは、これから変更されていくだろうと思います。

そこまで深く調べていないので、似たようなライブラリが既にあるかもしれません。

サンプル

README.md にもありますが、以下のように使うことができます。ただし、これは 2013/10/27 時点での API に基づいています。

概要

オフィシャルにリリースされている 7-Zip の DLL である 7z.dll を内部的に呼び出しています。

7zip 圧縮などでは、マルチスレッドで動作します。このあたりの挙動は 7z.dll の挙動に依存します。

Windows, Linux で動作する (はず) です。 Mac OSX はいずれ対応するだろうと思います。

gem にしてありますが、 rubygems にはまだ登録してません。
やったことがないので、まだ方法もよく分かってませんが、いずれ登録します。

作成のポイント

7z.dll の API では、コールバック関数を DLL に登録し、それを適宜呼び出してもらうようになっています。
このコールバック関数の呼び出しは、場合によっては 7z.dll 内で生成された別スレッドから行われることもあります。
これは、 GIL や GVL と呼ばれる Ruby Interpreter の Mutex を持っていない状態でコールバックが呼ばれるということを意味します。
この動作に対応するのが面倒でした。

また、 C++ で作成したので、こちらもやや面倒でした。

これらについては、別途、まとめておこうと思います。

2013年5月28日火曜日

Windows 上の Ruby 2.0 での sqlite3 の利用

Windows 上の Ruby 2.0 での sqlite3 の利用

最近は仕事が忙しすぎて、 mruby もブログも触る暇がないですが、久しぶりの備忘録です。
いくつか mruby などでリクエストをいただいていますが、なかなか対応できていなくて、本当にすみません。

MinGW 版 32bit Ruby 2.0 での sqlite3 の利用

2013年5月27日時点では、 sqlite3 の Windows 用 gem (version 1.3.7) には Ruby 2.0 向けのバイナリが入っていないようです。
そのため、 MinGW 版の 32bit Ruby 2.0 で Rails を使おうとすると、以下のようなエラーが出るようです。

私は RubyInstaller の 32bit 版を使わせていただいていますが、それに向けた sqlite3 のバイナリを置いておきます。
あくまで MinGW 版の 32bit Ruby 2.0 でしか確認していません。

準備とバイナリのダウンロード

下記のコマンドで、 sqlite3 をインストールしておきます。

このコマンドにより、 Ruby が C:/ruby にインストールされている場合は、 C:/ruby/lib/ruby/gems/2.0.0/gems/sqlite3-1.3.7-x86-mingw32 に gem がインストールされると思います。

続いて、 C:/ruby/lib/ruby/gems/2.0.0/gems/sqlite3-1.3.7-x86-mingw32/lib/sqlite3 の下に 2.0 ディレクトリを作成します。
おそらく 1.8, 1.9 というディレクトリは既にあるので、同列に 2.0 ディレクトリを作成します。

あらかじめ作成済みの sqlite3_native.so をダウンロードし、この 2.0 ディレクトリの下に置きます。

後は、普通に使えば大丈夫です。

以下に、作り方などの詳細を書いておきます。

Ruby 2.0 での sqlite3

Ruby から sqlite3 を使うには、 rubygems の sqlite3 を使うのが手軽です。
特に、 Ruby on Rails などでは、デフォルトでこの gem を使うようになっています。

しかし、 2013年5月27日時点では、 sqlite3 の Windows 用 gem には Ruby 2.0 向けのバイナリが入っていないようです。

ここでは、 Windows 上の 32bit 版 Ruby 2.0 で sqlite3 gem を使う方法について、書いておきます。
ただし、いずれは gem 側で解消されるだろうと思いますし、この方法はあくまで単なるパッチです。

Windows 上での Ruby 2.0 での sqlite3 gem の準備

いくつか方法はありますが、私は RubyInstaller を使わせていただいています。

Ruby や DevKit のダウンロードと展開

Ruby 2.0 のダウンロード
RubyInstaller のダウンロードページから、 Ruby 2.0.0-p*** をダウンロードし、好きなところに展開します。
ここでは、 C:/ruby と仮定します。
DevKit のダウンロード
RubyInstaller のダウンロードページから、 DevKit-mingw64-32-4.7.2-20130224-1151-sfx.exe をダウンロードし、好きなところに展開します。
ここでは、 C:/devkit と仮定します。
パスなどの初期設定
コマンドプロンプトを開き、以下のように ruby.exe へのパスを通しておきます。 また、続けて DevKit にもパスを通しておきます。 C:\devkit に移動し、以下のコマンドを実行します。 続いて、 DevKit の初期設定をしておきます。下記のような config.yml を作成します。 以下のコマンドを実行し、 DevKit の初期設定をします。

sqlite3 のダウンロードと展開

SQLite3 のソースコードをダウンロードページからダウンロードしておきます。
Source Code の欄にある sqlite-amalgamation-***.zip がビルドしやすくて便利です。

これを C:\sqlite3 などに展開し、このディレクトリに移動し、以下のコマンドで libsqlite3.a を作成します。

gem のインストール

以下のコマンドで、 sqlite3 gem をインストールします。

2012年11月27日火曜日

AutoIt を Ruby から使い、それを ocra で EXE 化する

AutoIt を Ruby から使い、それを ocra で EXE 化する

ピンポイントな内容で、あまり他の人の役に立つか分かりませんが、ちょっと調べたので書いておきます。

今回の目的は、「AutoIt を呼び出す Ruby スクリプトを ocra で EXE 化し、制限ユーザーでも実行できるようにする」です。

実現方法

予備知識として、ocra による EXE 化はできる前提です。

準備

  1. RubyInstaller から Ruby 1.9.3 をダウンロードし、インストールしておきます。また、bin ディレクトリにパスを通しておきます。
  2. 以下のコマンドで ocra をインストールしておきます。
  3. 続いて AutoIt から AutoIt をダウンロードし、インストールしておきます。
  4. AutoItX/AutoItX3.dll を Ruby の bin ディレクトリ以下にコピーしておきます。
  5. 以下の内容を記述した ruby.exe.manifest を Ruby の bin ディレクトリ以下に作成します。

これで事前準備は完了です。

EXE の生成

以下のような sample.rb を考えます。

これは電卓を起動し、3 + 4 を実行するスクリプトです。電卓のウィンドウを表す文字列は、OS によっては異なるかもしれません。

これをそのまま EXE 化して EXE ファイルを別の実機に持っていっても、AutoItX3.dll が無いので実行できません。AutoItX3.dll も別の実機にコピーし、前もって 「regsvr32 AutoItX3.dll」を実行しておけば可能ですが、これには管理者権限が必要です。

これを回避するために、以下のようなコマンドで EXE 化します。

このようにして生成された EXE ファイルは、制限ユーザーであってもそのまま実行でき、AutoItX3.dll を呼び出すことができます。

説明と補足

以下は、原理の説明などです。

AutoIt

AutoIt とは、Windows の GUI を自動操作することができるツールのことです。

Windows 上で GUI を含んだツールのテストをしたり、GUI プログラムの定型処理を自動化したりするには、それなりに面倒なことが多いのですが、この AutoIt を使うと、割と楽に自動化できます。
同様なことは、.NET からだと Windows 標準の UI Automation などでも可能です。

この AutoIt には、COM インターフェースも用意されており、 COM オブジェクトにアクセスできる言語からは、自由に機能を利用することができます。
実体は AutoItX3.dll で、AutoItX3.Control という ProgID でアクセスできます。

もちろん Ruby からも WIN32OLE を利用することで、呼び出すことができます。
上に書いたように、例えば Windows の電卓 (calc.exe) を起動し、3+4 を計算させる場合は、以下のようになります。

ただし、このスクリプトを実行できるようにするためには、AutoItX3.dll をあらかじめ Windows に登録する必要があります。
インストーラでインストールした場合は登録されているはずですが、自己解凍形式でインストールした場合は、以下のコマンドを管理者権限で実行し、AutoItX3.dll を Windows に登録しておかなければなりません。

EXE 化して実行するときの問題点

上記の Ruby スクリプトを ocra で EXE 化した場合、その EXE を実行する Windows マシンにも AutoItX3.dll がインストール済みでなければなりません。これは多少面倒ですし、管理者権限も必要になってしまいます。
今回の本題は、そのあたりをどうするかについてです。

基本的には、ocra で EXE 化する際に AutoItX3.dll も EXE に含めてしまい、かつスクリプトの内部から制限ユーザーでも呼び出せるようにするということになります。

マニフェストファイル

大きな流れとしては、AutoItX3.dll を EXE の中に組込み、かつマニフェストファイルを作成することで、その DLL ファイルを COM としてアクセスできるようにしてやる、ということになります。

マニフェストファイルとは、EXE ファイルのメタ情報のようなもので、ロードすべき DLL を指定したり、UAC 昇格が必要であることを指定したりする XML ファイルです。

このファイルを用いて、AutoItX3.dll の中に AutoItX3.Control という ProgID の COM オブジェクトがあることを明示し、ruby.exe からアクセスできるようにしてやります。
このマニフェストファイルは、実行する EXE に .manifest をつけた名前にします。

ocra では、ruby.exe やその他の DLL を Temp ディレクトリに展開し、その後 ruby.exe を実行します。
そのため、Temp ディレクトリに展開される際に、マニフェストファイルも展開されるようにすれば、ruby.exe が実行される際にマニフェストファイルで COM 用 DLL を指定することができます。

そのため、以下のようなコマンドで EXE ファイルを作成すれば、AutoItX3.dll も ruby.exe.manifest も ruby.exe と同じディレクトリに展開されるようになります。

これで、制限ユーザーであっても、AutoItX3.dll がインストールされていない状況であっても、Ruby スクリプトから AutoIt の COM インターフェースにアクセスできる EXE ができあがります。

ポイント

--dll を使っているところがポイントです。

前提として、ocra で EXE 化されたスクリプトは、まず一時フォルダに ruby.exe 本体やスクリプトや DLL などを展開し、さらに展開した ruby プログラムを実行する、という二段構成になっています。
細かく書くと、EXE を実行すると、ruby.exe 本体が置かれる bin ディレクトリ、ライブラリが置かれる lib ディレクトリ、ユーザーのソースコードが置かれる src ディレクトリの三つをそれぞれ展開し、その後 src 以下のソースコードを bin ディレクトリ以下の ruby.exe によって実行する、という流れになります。

EXE 作成時に --dll オプションを使って指定されたファイルは、EXE 実行時に bin ディレクトリ以下に展開されることになります。
そのため、上の例では、EXE 実行時には以下のような配置になります。

/
bin
ruby.exe
AutoItX3.dll
ruby.exe.manifest
lib
require されたライブラリが配置される。
src
sample.rb

上のように ruby.exe.manifest ファイルを配置することで、展開された ruby.exe が実行される際に、ruby.exe.manifest を読み込ませることができ、結果として AutoItX の COM インターフェースにアクセスできるようになります。

ちなみに、ocraruby として一つのバイナリにまとめてある Ruby の中にも、同様の仕組みで AutoItX3.dll を組み込んであります。

というわけで、何回か仕事で使うツールの内容が続きましたが、忙しかった時期も過ぎつつあり、そろそろこの週末ぐらいからは mruby を触れそうです。

2012年11月4日日曜日

ocraruby - Ruby を一つの EXE にまとめて、簡単に持ち運べるようにしておく

ocraruby - Ruby を一つの EXE にまとめて、簡単に持ち運べるようにしておく

体調を崩していることもあり、なかなか仕事以外で Ruby に触れる機会がないですが、これも業務で使うために家で作ったので公開しておきます。

単一のバイナリのみで、任意の Ruby スクリプトを実行したい

いろいろな Windows PC 上で作業する必要があったり、ちょっと Windows サーバーのメンテナンスをする必要がある場合などにあると便利なので、 ocra を使って単一のバイナリのみで任意の Ruby スクリプトを実行できるようにしたものを作っておきました。
任意とは言っても、gem などの外部ライブラリを必要とするものは動作しません。
あくまで、 Ruby の標準添付ライブラリのみを使用しているスクリプトが動作するということです。

とりあえず Windows で Ruby を触ってみたいというような、 Windows で Ruby を初めて使う方にもよいかもしれません。
サポートはできないですが。

ダウンロード

バイナリのダウンロードは GitHub のダウンロードページからお願いします。
この記事を書いている時点では、Ruby 1.9.3p286 を元にしたものがダウンロードできます。

ocraruby.exe
私が独断で選んだよく使う標準添付ライブラリが入っています。ネットワーク関係が入っていないのは、これらを入れるとサイズが大きくなるからです。
ocrarubyfull.exe
すべての標準添付ライブラリが入っています。
ocrarubylite.exe
標準添付ライブラリなし

仕組み

特に何のひねりがあるわけでもなく、引数で渡されたファイルを load するようなスクリプトを ocra で EXE 化しているだけです。

一応、 ARGV や $0 の設定はしてあるので、下記のようなスクリプトも期待通りに動作します。

注意

注意点としては、 ocra を使っているので、 Ruby 本体を Temp ディレクトリに展開する分だけ時間がかかってしまうということです。

ocrarubyfull.exe などでは、5秒以上かかるかもしれません。
これは展開に時間がかかっているのであり、 Ruby は遅いんだな、と勘違いしないでください。

来月になればきっと体調も戻り、時間もできると思うので、また色々やりたいことをやろうと思います。

oruby は既にそういう名前のものがあったので ocraruby にしておきました。

ocra による Ruby の EXE 化

ocra による Ruby の EXE 化

近頃はずっと仕事が忙しく、mruby を見たり遊んだりもまったくできないので、業務で使わせていただいている ocra について、主に社内用にメモしておきます。

JsMruby やその他のリクエストに対応できていなくてごめんなさい。

以前にも書きましたが、Ruby 1.8 のときに愛用させていただいていた Exerb は、現在主流の Ruby 1.9 には対応していないようです。
そのため、現在は Ruby 1.9 に対応している ocra を使わせていただいています。
今回はその ocra についてのメモです。
もっと詳細は、 GitHub の ocra のページを参照してください。

Ruby の EXE 化

ocra では、Ruby のスクリプトを Ruby 本体やライブラリとともに、一つの EXE にまとめてくれます。
実行する際は、できた EXE ファイルを、実行したい PC に置くだけで実行することができます。

EXE 化する際には、一度 ocra がそのスクリプトを実行し、スクリプトファイル中で require などで読み込まれたファイルやライブラリを、圧縮して一つのファイルにまとめてくれます。

逆に実行時には、スクリプトファイルや Ruby 本体やライブラリを Temp ディレクトリに展開してから実行されます。

ちなみに、Exerb では require を専用のものに置き換え、ファイルが require されたときに、EXE ファイル本体内に格納されているスクリプトファイルを実行するようになっています。

ocra で定義される定数や環境変数

ocra のように、コンパイル時に一度スクリプトを実行してみるようなものでは、以下の三つの状態をスクリプト内で区別したいときがよくあると思います。

  1. 通常の Ruby で実行されている
  2. ocra でコンパイルされている間に、 require をチェックするために実行されている
  3. ocra で EXE 化された後に、その EXE ファイル経由で実行されている

ocra では、この条件を簡単に見切るために以下の定数や環境変数を参照できます。

定数 Ocra
ocra でコンパイル中のみ定義されています。 EXE ファイル実行中は定義されていません。
そのため、例えば以下のように書けば、コンパイル中のみコードを実行させることができます。
環境変数 OCRA_EXECUTABLE
ocra によって生成された EXE ファイルを実行しているときのみ、実行中の EXE ファイルのフルパスが "\" 区切りで設定されます。
そのため、例えば以下のように書けば、 EXE ファイル実行中のみコードを実行することができます。

各環境での動作

ここでは以下のようなスクリプトを実行し、それらの値やカレントディレクトリなどの環境をまとめておきます。

通常の実行時

このスクリプトを C:/work/test/test.rb として c:/work/test で実行すると、以下のようになります。

EXE 化する時

ocra で EXE 化する場合、ocra によってスクリプトが実行され、その際に require で読み込まれたファイルやライブラリがチェックされます。
この require されるファイルチェックの場合には、以下のようになります。

このように、EXE 化する時には Ocra という定数が定義されます。
通常のスクリプトは、EXE 化する際には本処理を実行したくないことが多いので、たいていは以下のように書くことになると思います。

EXE 実行時

できた test.exe を実行すると、以下のようになります。

このように、EXE 実行時には OCRA_EXECUTABLE という環境変数が、 EXE ファイルのフルパスとして定義されます。

また、上で書いたように ocra はスクリプトファイルなどを Temp ディレクトリに展開した状態で実行しますが、 __FILE__ や $0 を見ると、確かに Temp ディレクトリの中に展開されていることが分かります。

EXE ファイルへの他のファイルの同梱

ocra は require で読み込んだファイルを自動で検出して EXE ファイルに含めてくれますが、明示的にスクリプトファイルやその他のファイルを含めることもできます。

やり方は簡単で、 EXE を作るときに ocra.bat に渡す引数に加えてやればよいだけです。

こうすると、 EXE 実行時には、 7za.exe も sample.rb と同じディレクトリに展開されます。
つまり、 sample.rb 内であれば File.dirname(__FILE__) + "/7za.exe" でパスを得ることができるので、別の EXE を同梱して Ruby スクリプト実行時に参照することも可能です。

2012年9月17日月曜日

JsMruby - mruby NPAPI plugin

JsMruby - mruby NPAPI plugin

mruby を Firefox, Google Chrome で動作するようにしました。

mruby

mruby on EFI Shell でも書いたように、 mruby は、Ruby の作者であるまつもとさんが開発されている「組込み向け軽量 Ruby」の実装です。

前回は、単に EFI Shell 上で動くようにコンパイルしただけでしたが、今回は Firefox, Chrome 上から mruby スクリプトを実行でき、 JavaScript の関数も呼べるようにしてみました。

JsMruby - mruby NPAPI plugin

Firefox, Chrome 上で動作する mruby の NPAPI plugin を作ってみました。

サンプル

このプラグインを Firefox, Chrome にインストールしていると、以下のような書き方ができます。

ちょっと長いですが、全文を載せます。

この HTML ファイルを開き、 Draw ボタンをクリックすると、 canvas に正方形が描かれます。

もう少し長いサンプルとしては、マンデルブロ集合を描くサンプルを参照してみてください。
このサンプルでは Draw を押してから 3 秒ぐらい待ってから描画されます。

このように、割とシームレスに JavaScript のメソッドが呼べ、これまで JavaScript で書かねばならなかったものを mruby で書けるようになります。

概要

現在のところ、以下のようなことができます。上のサンプルを参照しながら読んでみてください。
ただし、これらは今後の実装次第で変わっていくだろうと思います。

JavaScript から mruby スクリプトの実行

object タグで NPAPI プラグインを読み込みます。

このオブジェクトに対して、load メソッドで mruby スクリプトをパースし、実行できます。

その後は、 send メソッドで、 mruby スクリプト内で定義したメソッドを直接実行することができます。

mruby から JavaScript スクリプトの実行

逆に mruby スクリプト側からは、 JsObj.get() メソッドで JavaScript のオブジェクトを取得できます。

現在の実装では、 Array, String, Integer, Double などに関しては、 mruby の該当するオブジェクトに変換されます。
また、 DOM オブジェクトなどの場合は、 DOM オブジェクトそのものをラップした JsObj クラスのインスタンスが返され、そのインスタンスに対しては、通常の DOM オブジェクトに対するメソッドを呼び出すことができます。(createElement などを呼べます)
function の場合は、 mruby の Proc オブジェクトのようなものが返されます。(ただし call メソッドでしか呼び出せません)

制限

現在のものは、あくまでアルファ版なのでクラッシュしたりうまく動かなかったりするだろうと思います。

特に、例外まわりやガベージコレクションまわりについては、それほどきちんと考えて書いていないので、不具合が多く眠っているだろうと思います。

ダウンロードとインストール

いずれも GitHub のページからダウンロードできます。

まだ Windows 向けにしか作成していないので、 Mac などでは動きません。

Firefox
jsmruby_0_0_1.xpi をダウンロードし、そのファイルを Firefox に Drag & Drop するとインストールできます。
このアドオンは、単に JsMruby の NPAPI プラグインを含んでいるだけのものです。
余談ですが、 NPAPI プラグインを含むアドオンを作成する場合は、 install.rdf 内に <em:unpack>true</em:unpack> の一行を書く必要があります。
Chrome
jsmruby_0_0_1.zip をダウンロードし、そのファイルをローカルで展開します。
Chrome の「ツール」-「拡張機能」を開いて「デベロッパーモード」にチェックを入れ、「パッケージ化されていない拡張機能を読み込む」から展開したディレクトリを指定して読み込みます。

ソースコードは GitHub の JsMruby のページで見られます。
まだ整理できていないですが、そのうち整理します。
また、 Mozilla のサンプルソースを使っている関係上、一部のファイルのみ NPL ですが、それ以外のファイルに関しては MIT ライセンスとする予定です。

ブラウザ上の Ruby

既に本家の Ruby では、 Ruby 2.0 にて Chrome の NaCl に対応することが表明されています。
似たようなのを作っておいて書くのも何ですが、私はこちらにとても期待しています。

また、 mruby についても既に試されている方がいらっしゃいます。

私はまだそれほど NaCl や PPAPI については勉強していないのと、 Chrome よりも Firefox が好きなので Firefox で動かしたい、という動機があったので、この NPAPI 版のプラグインを作ってみました。
というか、 masuidrive さんの MobiRuby の講演を聞いて、「いいなぁ」と思って自分も何か作りたくなったというのが本音です。

EFI Shell 版の方は、使いたい人はあまりいないと思いますが、 Windows 8 リリースまでにはまとめようかと思っています。

暇を見つけて Mac 版や、ドキュメントの作成などをします。たぶん。

2012年6月3日日曜日

Add Subversion Links の Redmine 2.0 対応

Add Subversion Links プラグインを Redmine 2.0 対応したので、そのときのメモです。

Add Subversion Links プラグイン

Add Subversion Links プラグインは、Redmine のプラグインです。

このプラグインを入れておくと、 Redmine のチケット画面やリポジトリ画面に、 Subversion リポジトリ本体へのリンクが勝手に追加されます。

詳しくは GitHub の説明ページを見てください。

Redmine 2.0 対応

Redmine 2.0 では、ベースとなる Rails のバージョンが 3.2 に上がりました。
その結果、多くのプラグインの互換性がなくなったので、 Redmine 2.0 にバージョンアップしていない人も多いと思います。

Add Subversion Links の場合も、動かなくなっていました。

一つめの原因は、 Rails 3 で導入された html_safe に関するものでした。
Rails 3 では、「エスケープされていない String」と「エスケープ済みの SafeBuffer」を区別して扱うことで、エスケープ漏れや、多重のエスケープを回避しています。

Add Subversion Links では、 Subversion リポジトリへのリンクを生成する部分で、一部、エスケープしていない文字が入っており、結果として正しい表示になっていませんでした。
(エスケープしていない文字は、単なる空白だったので、最初は何が起こったかよく分かりませんでした。)

もう一つは、 image_path メソッドが削除されたことでした。

原因はよく調べていませんが、プラグインが持つ image assets の場所を返すメソッド image_path がなくなっていました。

Add Subversion Links では、一部、 JavaScript で動的にリンクを生成している箇所があり、そのリンク画像生成のために image_path を使っていたので、エラーになっていました。
結局、こちらは image_tag というタグ自体を生成するメソッドは残っていたので、こちらを使うように処理を変更することで対応しました。

Add Subversion Links プラグインの仕組み

Redmine 2.0 対応とは関係ないですが、このプラグインの仕組みを少しだけ書いておきます。

Redmine プラグインの作り方は、他にたくさんの優れたページがあるので、ここでは書きません。

ここでは、 Add Subversion Links プラグインを作る上での話について少し書きます。

メソッドの上書き

プラグインを入れると、下記のような Subversion のアイコンが現れ、リポジトリ本体へのリンクが追加されます。

example

これは、 application.js 内の link_to_revision メソッドを上書きして、アイコンを追加してやるだけで簡単に実現できました。

JavaScript による書き換え

面倒だったのは、下記の Name 列のような、個々のファイルへのリンクでした。

example2

こちらの方は、 link_to_revision のような、専用のメソッドがあるわけではないので、プラグインからメソッドを上書きして機能を変更するということが、簡単にはできなさそうでした。

というわけで、方針を変えて、 JavaScript で load 時に動的に追加してやることにしました。

しかし、この Name 列の内容は、 Ajax で動的に追加されるものであるため、 load 時にリンクを作るだけでは、後から Ajax で追加された要素に対してリンクを生成することができませんでした。

そこで、面倒だったのですが、 Ajax で Name 列の要素が生成されたときに呼ばれるコールバック関数 (prototype.js の Ajax.Updater の onSuccess 属性に渡す関数) をフックしてやり、そのフック関数の中で Subversion リポジトリ本体へのリンクを生成してやりました。

コードとしては以下のようになっており、 onSuccess で呼ばれる scmEntryLoaded を Rails の alias_method_chain のように上書きしてやることで、リンクを生成する処理を追加しています。
また、 Ajax.Updater による要素の追加は、 onSuccess の直後に行われるので、実際にリンクを追加する addRepositoryLinkInRepositoryPage は、 setTimeout を使って遅らせています。

これで、めでたく Redmine 2.0 に対応できました。
JavaScript の部分以外はテストも書いたので、少しは (面倒であまり楽しくない) メンテナンスも楽になるはずです。

2012年4月15日日曜日

Ruby の Garbage Collection に関するメモ

Exerb でのバグ報告に関連して、Ruby の Garbage Collection について調べていたときのメモです。

Ruby の EXE 化

Ruby 勉強会の準備をしていて、「Windows 上での Ruby の EXE 化」についてまとめていました。
私は Ruby 1.9.3 では Ocra を使っていますが、Ruby 1.8.7 の頃は Exerb を使っていました。

どちらもとてもよいツールでお世話になっていますが、どちらかというとレシピファイルで同梱ファイルを指定できる Exerb の方が好きです。
ただし、Exerb は Ruby 1.9 には対応していません。
もともとの作者の Yuya さんは、今は開発に関わっていないらしく、このまま 1.9 系列の開発はされないのかもしれません。
ちょっと残念です。

Exerb で報告されていた挙動

2月ぐらいに、Integer#to_s でごくまれに変な文字列になるという報告がされていました。
で、今回、勉強会の資料を作るついでに気になったので (というか、重大なバグがツールに含まれていると勉強会でおすすめしづらいので) 調べてみました。

たとえば、以下のようなソースで、ごくまれに変な文字列が表示されます。

原因

原因を調べるために、何ヶ月かぶりに Windows を立ち上げて調べてみました。

原因は、Bignum クラスのメソッド to_s 内で、テンポラリに作られた Bignum オブジェクトが、意図せず Garbage Collection で解放されてしまうためでした。

なお、これは Ruby 1.8.7 での話です。Ruby 1.9 では、このあたりがもう少し高速なアルゴリズムも併用するように変わった関係で、発生しません。

bignum.c の rb_big2str0 関数に以下のようなところがあります。

ここでは、(1) で変換元の値 x をコピーして、その中の実際に数値を保持している配列のアドレスを (2) でポインタ ds に取り出しています。
その後、戻り値用の文字列オブジェクトを (3) で用意し、これ以降で変換した後の文字列を生成しています。

障害が発生する場合は、(1) で生成したオブジェクトが (3) で解放されてしまっていました。
その結果、(3) 以降で t の中身である ds を参照しても、変な値しか残っていない状態になっていました。

Ruby の Garbage Collection

Ruby のスクリプトの中で new されたオブジェクトならともかく、C言語で書かれた Ruby ライブラリや、Ruby 自体のソースコードの中で生成されたオブジェクト (今回の t のようなもの) が、どうやって自動で解放されているのか、ずっと不思議でした。
オブジェクトの一覧は簡単に取得できるはずですが、どれを解放の対象とするかどうかの判断が難しいのではないかと思っていました。

WEB 上で資料を探してみると、少し古いですがRuby ソースコード完全解説の中で詳しく解説されていました。

こちらを読みながら、実際に現在のソースコードを見ていると、C言語ライブラリなどで生成されたオブジェクトは、スタック上に Ruby オブジェクトらしきものを探し、発見されればそれは Garbage Collection の対象としないという仕組みになっているようでした。

率直な感想としては、「そんな適当に判断して大丈夫なの」というものでしたが、Garbage Collection は、解放可能なものをすべて解放する必要はなく、解放すべきでないものを解放してしまわなければよいものなので、これでもうまくいくようです。

ちなみに、今回の障害は、上記ソースコードの t と ds が運悪くスタック上の同じ番地にとられてしまうと発生していました。
Exerb のコンパイルオプションだと、偶然そうなっていたようです。
その結果、(3) の時点では既にスタック上からは t そのものは消えており、Garbage Collection による解放の対象となってしまっていました。

修正

よって、ds を参照している間は、t もスタック上に残っているようにすれば、間違って解放されることもなくなります。

Ruby のソースコードでは、このためのマクロがあり、RB_GC_GUARD が定義されています。

これを使い、RB_GC_GUARD(t); を ds を最後に参照している箇所より後に書けば、うまくいくようになります。

マクロ RB_GC_GUARD は、volatile 修飾子を使って、引数である t を強制的にその場で参照するようにするものです。
その結果、t が ds と同じメモリ番地に割り当てられるようなこともなくなり、きちんと t がスタック上に残るようになります。

というわけで、念の為に Ruby 本家の方も直してもらおうとチケットを書きかけましたが、Exerb のメーリングリストを見ておられたのか、Ruby コミッタの nobu さんが既に修正されていました。

nobu さん、ありがとうございます。
そして、Ruby 1.8.7 の ChangeLog に私の名前も入ることになりました。わーい。

2011年12月12日月曜日

Redmine Plugin で ApplicationHelper を拡張する際の注意

Redmine Plugin で ApplicationHelper を拡張する際の注意

Windows PC の調子が悪いので、NPAPI プラグインの作り方は少し延期です。

今回は、Redmine 1.3.0もリリースされたので、 Redmine Plugin で ApplicationHelper を拡張する際に悩んだ問題について書きます。

Redmine プラグインで、既存のクラスやモジュールを拡張する

Redmine プラグインを作成し、 ApplicationHelper などの既存のクラスやモジュールを拡張する場合、基本的には以下のようにします。

  1. プラグインのlib以下にredmine_sample_plugin_application_helper_patch.rbのようなファイルを作成し、以下のように記述します。
  2. init.rb内部で、redmine_sample_plugin_application_helper_patch.rbをrequireします。

上に書いたように、一般的にはApplicationHelperを再定義せずに、追加したり置換したりしたいメソッドを別モジュールにまとめた上で、これをインクルードさせることが多いです。

View のフック関数からの呼び出し

Redmine のプラグインへのフレームワークとして、View の決められた場所に、プラグインのコードを埋め込む、という仕組みがあります。

ここでは説明しませんが、知りたい方はRedmine自体に手を入れずに見た目を変更する方法などをご覧ください。

いずれにせよ、Redmine::Hook::ViewListenerを継承したクラスを作成し、その中にあらかじめ定められた名前のフックメソッドを定義することで、View に任意のコードを追加できます。

View のフック関数から、追加したメソッドの呼び出し

上のSamplePluginApplicationHelperPatchのように定義した場合、新規に定義したメソッド (上の例ではsample_instance_method) は、View のフック関数から呼ぶことができません。

ApplicationHelper 内のメソッドは、すべてのコントローラーや View から呼べるはずなのですが、呼ぶことができないのです。

呼び出せない理由

ApplicationHelperは、Redmine::Hook::ViewListener内で適切にincludeされています。
また、上で定義したサンプルでは、確かにSamplePluginApplicationHelperPatch::InstanceMethodがApplicationHelperにインクルードされています。
そのため、間接的にSamplePluginApplicationHelperPatch::InstanceMethodはRedmine::Hook::ViewListenerからインクルードされているように思えます。

しかし、実際にはそうなりません。

Redmine::Hook::ViewListenerがApplicationHelperをインクルードするのは、プラグインの各ファイルがロードされるよりも早いタイミングで行われます。
そのため、Redmine::Hook::ViewListenerがApplicationHelperをインクルードする頃には、まだSamplePluginApplicationHelperPatch::InstanceMethodはApplicationHelperにインクルードされていません。

Ruby では、includeしたタイミングで継承関係が決定します。

例を挙げると、以下のようになります。

上の例で、B.ancestorsにCが含まれてもよさそうですが、含まれません。

これは奇妙なように思いますが、 Ruby の仕様であり、どうしようもありません。

余談ですが、 Ruby 作者のまつもとさんも、これは奇妙に思っているらしく、Matzにっきの Traits の項目に、今後mixを追加しようと思っている旨が書かれています。

そのため、もともとの Redmine の ViewListener に関しても同様に、Redmine::Hook::ViewListenerがApplicationHelperをインクルードするタイミングでは、まだSamplePluginApplicationHelperPatch::InstanceMethodがApplicationHelperにインクルードされていないので、結果としてRedmine::Hook::ViewListenerの ancestors にはSamplePluginApplicationHelperPatch::InstanceMethodが含まれないことになります。

回避策

これは、例えば以下のようにすれば回避することができます。

このようにすることで、sample_instance_method自体はApplicationHelperに直接追加されるので、Redmine::Hook::ViewListenerからでも呼び出すことが可能になります。

もちろん、base.class_eval doの内部で、直接sample_instance_methodを定義しても呼び出すことができますが、このあたりはコードの見易さの問題で、好みの分かれるところだろうと思います。

2011年11月15日火曜日

requireとrequire_dependencyとRuby 1.8.7とRuby 1.9.2

requireとrequire_dependencyと Ruby 1.8.7 と Ruby 1.9.2

先日、Redmineの Version 1.2.2 もリリースされたので、プラグインを書くときに気になった、requireとrequire_dependencyの違いおよびRuby のバージョンによるrequireの挙動の違いについて書こうと思います。

とは言っても、よく書かれている「カレントディレクトリがロードパスに含まれなくなった」ということではありません。

なお、これは Rails 2.3.11, 3.1.0 を見て書いています。また、Redmine は Version 1.2.2 を見ながら書いています。

requireとrequire_dependency

Rails のコードを見たことがある人は分かると思いますが、Rails のコードではrequireの代わりにrequire_dependencyが使われていることに気付くと思います。

それぞれ簡単にまとめると以下のようになります。

require
Ruby の組込み関数。正確には、Kernelモジュールのメソッド。
引数で渡された .rb ファイルなどを読み込む。ただし、既に読み込み済みのファイルは読み込まない。
読み込まれたモジュールは$", $LOADED_FEATURESに格納されて管理される。
require_dependency
Rails が定義した独自のメソッド。activesupport/lib/active_support/dependencies.rbで定義されている。
引数で渡された .rb ファイルなどを読み込む。ただし、既に読み込み済みのファイルは読み込まない。development モードで実行している場合はloadを使って読み込まれる。production モードで実行している場合は、requireを使って読み込まれる。
これは、development モードで実行している場合は、動的にファイルを何度も読み込む必要があるため。

Redmine プラグインで注意すること

結論から書くと、Redmine プラグインの中では、Redmine のモデル、ビュー、コントローラー、ヘルパーを読み込むには、require_dependencyを使うべしということです。

ここで、requireを使うと失敗します。

上の説明で書いたように、production モードで実行するならどちらでも結局requireが呼ばれるので変わらない、と思うかもしれませんが、そうではありません。

以下で順に説明します。

Ruby 1.8.7 と Ruby 1.9.2 でのrequireの挙動の違い

まず、requireの挙動をもう少し詳しく見てみます。

Ruby 1.8.7 でのrequireの挙動

main.rbから、required.rbを、下記のようにrequireする場合を考えます。

ここでmain.rbを実行すると、出力は下記のようになり、二回目のrequireではrequired.rbがロードされていないことが分かります。

これは当然の挙動だと思います。

次に、以下のように、相対パスや絶対パスを指定してrequired.rbを読み込んでみます。

出力は以下のようになります。

このように、予想に反して (かどうかは分かりませんが) 三回とも読み込まれています。

Ruby 1.9.2 でのrequireの挙動

一方で、同じコードを Ruby 1.9.2 で実行してみます。

ただし、Ruby 1.9.2 では、カレントディレクトリがロードパスに含まれなくなっているので、先頭で追加してやった状態で実行してみます。

出力は以下のようになります。

今回は、一度しか読み込まれませんでした。

このように、Ruby のバージョンによってrequireの挙動は微妙に異なります。

Ruby 自体のソースを見れば分かりますが、「既に読み込まれたか」を表すリストである$", $LOADED_FEATURESに記憶する方法が、Ruby 1.8.7 と Ruby 1.9.2 では異なるようです。

Ruby 1.8.7 では指定されたファイルをそのままの形で$LOADED_FEATURESに覚えておきます。 そのため、require("required")やrequire("./required")など、指定方法が異なると、複数回ロードされることとなります。

一方で、Ruby 1.9.2 では、常に絶対パスに直して覚えるようになっています。 その結果、実際に同じファイルであれば、指定方法が異なっても複数回ロードされることはありません。

Redmine プラグインでrequire_dependencyを使わないと正しく動作しない理由

以上を踏まえて、なぜ production モードであっても Redmine プラグインでrequire_dependencyを使わないとうまく動作しないのか、について書きます。

Redmine は、現在も Ruby 1.8.7 で動作しています。

そのため、あるファイルをrequireする際に、絶対パスで指定した箇所とファイル名だけを指定した箇所がある場合、そのファイルは二重にロードされることになります。

一方で、require_dependencyでは、コードを追うと分かりますが、最終的に引数のファイル名を絶対パスに展開してからrequireもしくはloadします。

ここで、以下のように書いてしまった場合を考えてみます。

この例では、ApplicationHelperモジュールのformat_activity_titleメソッドを上書きしています。

しかし、このファイルとは別のファイルでrequire_dependency("application_helper")と書かれていると、そちらではapplication_helperが絶対パスに展開されてから、requireに渡されることになります。

そのため、ファイル名だけを指定してrequire("application_helper")としたときとは別のファイルとして扱われてしまい、別途、application_helper.rbがロードされることになります。

その結果、せっかく上書きしたメソッドが再度上書きされてしまい、変更が反映されなかったように見えてしまいます。

というわけで、素直にrequire_dependencyを使うようにするのがよいと思います。

2011年11月7日月曜日

Redmine プラグイン その2

Redmine プラグイン その2

昨日の続きです。

Redmine プラグインの雛形の作成

Redmine のプラグインを作成するには、以下のようなコマンドを実行します。 Redmine 1.2.1 では、まだ Rails 2.3.X 系統なので、ruby script/generateでプラグインを作成します。

このコマンドにより、vendor/pluginsディレクトリの下にredmine_sample_pluginというディレクトリが作られ、プラグインの雛形が生成されます。

プラグインの仕組み

今回は、簡単のためにデータベースへの保存などは行わず、表示方法をカスタマイズしたり、機能を追加したりする場合について書きます。

init.rbからrequireする

この雛形に含まれるファイルで、最重要なものはinit.rbです。

このファイルは、Redmine が起動する際に自動で読み込まれます。

また、読み込まれる順序は、Redmine の Core ファイルが読み込まれた後です。

前回のオープンクラスの部分で書きましたが、この順序で読み込まれることが保証されているので、init.rbの中で Redmine 関係のクラス (例えばIssueクラス) を再定義することにより、自由にメソッドを追加、削除、上書きすることができます。

例えば、以下のように書いたファイルをlib以下に置き、init.rbからrequireすると、Issueクラスにopen?メソッドを追加することができます。 (ただし、後で書くように、普通はもう少し整理された定型の書き方があります。)

このように書いたファイルをinit.rbからrequireすることで、新たにIssueクラスに新しいメソッドを追加することができます。

重要なポイントは、Issueクラスという既存のクラスへの変更が、既存のファイルであるapp/models/issue.rbをまったく変更することなく可能となっている点です。

これは、Issueクラスのメソッドの全体が分かりにくいという大きな欠点もできますが、プラグインを配布する際に、特定のディレクトリにファイルを展開するだけでよいという、モジュールの可搬性という点において大きな利点を生むことになります。

実際のファイルは、以下のようにして、インスタンスメソッドやクラスメソッドを分離して記述することが多いです。

app以下で上書きする

これとは別に、appディレクトリ以下に直接ファイルを置くことによって、既定の動作を上書きして変更することもできます。

例えば、app/views/issues/_edit.rhtmlの内容を少し変更したものをvendor/plugins/redmine_sample_plugin/app/views/issues/_edit.rhtmlに置くことにより、もともとの_edit.rhtmlではなく、redmine_sample_plugin以下に置かれた_edit.rhtmlを使わせることができます。

これは、Rails がファイルを検索する順序として、各プラグインのapp以下を探し、見つからなければルートディレクトリ直下のappディレクトリを探すとなっているためです。

そのため、同名のファイルをプラグインのapp以下に同じディレクトリ構成で置いてやることにより、自由にデフォルトのファイルを置き換えることができます。

簡単に表示やデフォルトの挙動をカスタマイズする程度であれば、このあたりを理解するだけで比較的自由に変更することができると思います。

一方で、あまりに自由度が高いので、これをやりすぎると、全体の見通しが悪くなったり、他のプラグインとの関係で思わぬ副作用が出たりするかもしれません。

Rails は、そもそも組込みのクラスにもいろいろなメソッドを (勝手に) 追加しています。こういう部分は、きちんとした設計のもとに行えば、非常に使い勝手が上がりますが、下手に追加すると、混乱を招くだけです。

Rails では、きちんとした設計のもとに行うことで、利便性を実現しています。

それと同じように、プラグインに対してもユーザーが自由にカスタマイズできるようにしておき、「自己責任でプログラマが自由にしてね」という余地をあえて残すような設計にしているのかもしれません。

2011年11月6日日曜日

Redmine プラグイン

Redmine プラグイン

以前に Redmine プラグインの練習として作ってみたWiki GChart Formula Pluginの紹介をかねて、Redmine プラグインについて書きます。

オープンクラス

Redmine, Rails に限った話ではありませんが、Ruby のクラスは、オープンです。

Redmine や Rails のプラグインは、このオープンクラスの仕組みをうまく使っているので、今回はその説明を書くことにします。

「クラスがオープンである」というのは、例えば、一度クラスを定義した後からでも、自由にメソッドの追加や上書きなどができるということです。

例として、以下のような Ruby のプログラムを考えます。Testクラスが二回定義されていることに注意してください。

このスクリプトを実行すると以下のように出力されます。

old
new
ok

この例では、(1) で定義したメソッドprintが (4) で再定義され上書きされます。また、(5) で新しいメソッドprint2が新規に追加されています。

C# の部分クラス定義との違い

これは、C# の部分クラス定義の機能を使って、複数ファイルでクラスの各メソッドを定義できることと似た部分があります。

しかし、Ruby ではクラスの定義自体も通常のメソッド呼び出しなどと同じように実行時に行われる点が大きく違います。

C# などでは、定義自体が分割されていようがなかろうが、クラスの定義自体はコンパイル時に解釈され、実行時にクラスのメソッドが途中で動的に追加されたり置き換わったりすることはありません。

そのため、C# でもともと提供されている既存のクラスに対してメソッドを追加するようなことはできません。 また、(1), (4) のように同名のメソッドを定義することもできません。 というか、後から定義した方で上書きできたとしても、実行時にはその片方の定義しか使われないので、意味がありません。

一方で、Ruby ではそれらが可能です。

そのため、上に書いたコードは以下のように動きます。

  • (2) の段階では、まだ (4) の再定義がされていないので、(1) で定義された通りoldが出力されます。
  • (3) の段階では、まだ (5) の定義がされていないので、print2などというメソッドは存在せず、エラーになります。
  • (6) の段階では、(4) の再定義の後なので、newが出力されます。(1) で定義されていた、古いprintメソッドを呼ぶ方法はありません。
  • (7) の段階では、(5) の定義の後なので、okが出力されます。

このように、クラス定義も実行時に行われ、かつクラスがオープンであるために、後から既存クラスの挙動を変更することが非常に柔軟に行えます。

これらの挙動を利用することで、Redmine や Rails では、プラグイン側から Redmine や Rails のクラスを上書きすることにより、自由なカスタマイズをすることができます。

続く。