LANG=ja_JP.SJIS の場合、問題は出ないようなのだが、
2013-02-26 現在 Cygwin 上の Ruby-1.9.3p327 で win32/registry を使う際、LANG=ja_JP.UTF-8 になっていると、以下のようなコードでエラーが出る。
どうも、each_value する際 Win32::Registry::API::RegEnumValueA() の戻り値が 0 以外になったら、raise させた Win32::Registry::Error を rescue する事で each_value から break させているようなのだが、raise させた Win32::Registry::Error でエラーメッセージのエンコーディングを変換する部分で不具合が出ているらしい。
FormatMessageA() は、日本語環境だと CP932 で書式化したメッセージを生成するが、それを force_encoding(Encoding.find(Encoding.locale_charmap)) すると、LANG=ja_JP.UTF-8 だと Encoding.locale_charmap は UTF-8 なので CP932 の文字列を UTF-8 で .force_encoding() している状態になっており、その直後で .tr() した際、不正な UTF-8 文字列として検出され腐っているらしい。
根本的な原因は、cygwin の場合、LANG の設定でロケールが変わっちゃうので FormatMessageA() が返す文字列のエンコーディングと Encoding.locale_charmap が必ずしも一致しないことが原因のようだ。
と言うことで、問題のコードと改善方法をまとめると以下のような感じ。
使っている Windows が何国語版かによって FormatMessageA() が返す文字列のエンコーディングが変わっちゃいそう(CP932 である保証がなさそう)なので FormatMessageW() 使って UTF-16LE 決め打ちしたほうが良いんじゃないかと思う。
と言うことで、以下のようなモンキーパッチを当てとけば不具合は解消する模様。
(補足: 2014-03-18: 以前公開してたコードは文字列の確保が半分しかできていなかったので要注意)
モンキーパッチじゃなくて win32/registry.rb の 172 行目付近にパッチ当てたほうが建設的じゃないかって気もする。
追記: 2014-03-18:
Ruby 2.1.0-preview1 以降、この問題は解決されている。
但し、2014-02-24 にリリースされた旧版の最新版である 2.0.0-p451, 1.9.3-p545 では、未解決のままなので何らかの対応が必要。
2013-02-26 現在 Cygwin 上の Ruby-1.9.3p327 で win32/registry を使う際、LANG=ja_JP.UTF-8 になっていると、以下のようなコードでエラーが出る。
#!/usr/bin/env ruby require 'win32/registry' Win32::Registry::HKEY_LOCAL_MACHINE.open('SOFTWARE\Microsoft\Windows\CurrentVersion\Run') do |reg| reg.each_value do |name, type, data| puts name end end
$ ./regtest.rb IME14 JPN Setup Adobe ARM LifeCam SunJavaUpdateSched /usr/lib/ruby/1.9.1/win32/registry.rb:173:in `tr': invalid byte sequence in UTF-8 (ArgumentError) from /usr/lib/ruby/1.9.1/win32/registry.rb:173:in `initialize' from /usr/lib/ruby/1.9.1/win32/registry.rb:231:in `exception' from /usr/lib/ruby/1.9.1/win32/registry.rb:231:in `raise' from /usr/lib/ruby/1.9.1/win32/registry.rb:231:in `check' from /usr/lib/ruby/1.9.1/win32/registry.rb:269:in `EnumValue' from /usr/lib/ruby/1.9.1/win32/registry.rb:524:in `each_value' from ./regtest.rb:4:in `block in <main>' from /usr/lib/ruby/1.9.1/win32/registry.rb:389:in `open' from /usr/lib/ruby/1.9.1/win32/registry.rb:496:in `open' from ./regtest.rb:3:in `<main>'
FormatMessageA() は、日本語環境だと CP932 で書式化したメッセージを生成するが、それを force_encoding(Encoding.find(Encoding.locale_charmap)) すると、LANG=ja_JP.UTF-8 だと Encoding.locale_charmap は UTF-8 なので CP932 の文字列を UTF-8 で .force_encoding() している状態になっており、その直後で .tr() した際、不正な UTF-8 文字列として検出され腐っているらしい。
根本的な原因は、cygwin の場合、LANG の設定でロケールが変わっちゃうので FormatMessageA() が返す文字列のエンコーディングと Encoding.locale_charmap が必ずしも一致しないことが原因のようだ。
と言うことで、問題のコードと改善方法をまとめると以下のような感じ。
#!/usr/bin/env ruby require 'dl/import' module Kernel32 extend DL::Importer dlload "kernel32.dll" end FormatMessageA = Kernel32.extern "int FormatMessageA(int, void *, int, int, void *, int, void *)", :stdcall FormatMessageW = Kernel32.extern "int FormatMessageW(int, void *, int, int, void *, int, void *)", :stdcall code = 259 # = "データはこれ以上ありません。" msgA = "\0".force_encoding(Encoding::ASCII_8BIT) * 1024 lenA = FormatMessageA.call(0x1200, 0, code, 0, msgA, 1024, 0) msgW = "\0".force_encoding(Encoding::ASCII_8BIT) * (1024 / 2) lenW = FormatMessageW.call(0x1200, 0, code, 0, msgW, 1024 / 2, 0) * 2 # Ruby-1.9.3p327 p msgA[0, lenA].force_encoding(Encoding.find(Encoding.locale_charmap)) p msgA[0, lenA].force_encoding(Encoding.find(Encoding.locale_charmap)).encoding # proposal1 p msgA[0, lenA].force_encoding("CP932").encode(Encoding.find(Encoding.locale_charmap)) p msgA[0, lenA].force_encoding("CP932").encode(Encoding.find(Encoding.locale_charmap)).encoding # proposal2 p msgW[0, lenW].force_encoding("UTF-16LE").encode(Encoding.find(Encoding.locale_charmap)) p msgW[0, lenW].force_encoding("UTF-16LE").encode(Encoding.find(Encoding.locale_charmap)).encoding
$ echo $LANG ja_JP.UTF-8 $ ruby -e "p __ENCODING__" #<Encoding:UTF-8> $ ./FormMessageTest.rb "\x83f\x81[\x83^\x82?\xB1\x82\xEA\x88??\x82\xE8\x82?\xB9\x82\xF1\x81B\r\n" #<Encoding:UTF-8> "データはこれ以上ありません。\r\n" #<Encoding:UTF-8> "データはこれ以上ありません。\r\n" #<Encoding:UTF-8>
と言うことで、以下のようなモンキーパッチを当てとけば不具合は解消する模様。
(補足: 2014-03-18: 以前公開してたコードは文字列の確保が半分しかできていなかったので要注意)
begin Win32::Registry::Error.new(259) rescue ArgumentError => e if e.message == "invalid byte sequence in UTF-8" class Win32::Registry::Error FormatMessageW = Kernel32.extern "int FormatMessageW(int, void *, int, int, void *, int, void *)", :stdcall def initialize(code) @code = code msg = "\0\0".force_encoding(Encoding::UTF_16LE) * 1024 len = FormatMessageW.call(0x1200, 0, code, 0, msg, msg.size, 0) msg = msg[0, len].encode(Encoding.find(Encoding.locale_charmap)) super msg.tr("\r".encode(msg.encoding), '').chomp end end else raise e end end
追記: 2014-03-18:
Ruby 2.1.0-preview1 以降、この問題は解決されている。
但し、2014-02-24 にリリースされた旧版の最新版である 2.0.0-p451, 1.9.3-p545 では、未解決のままなので何らかの対応が必要。
- Cygwin - RubyGems # Troubleshooting # 2014-03-03: UTF-8 環境で失敗する
タグ
コメントをかく