Ruby から Win32API を叩いて文字コード変換

意外と需要があるんじゃないかと思って書いてみた。Ruby 1.9 用です。Codepage は Code Page Identifiers あたりを見ればいいのですが、この API では全て叩けるわけじゃないので、改めて MLang 版を書くかも。

require 'dl/import'

module MSEncode
  extend DL::Importer
  dlload "kernel32"

  typealias("UINT", "unsigned int")
  typealias("DWORD", "unsigned long")
  typealias("LPCSTR", "char *")
  typealias("LPWSTR", "wchar_t *")
  typealias("LPCWSTR", "wchar_t *")
  typealias("LPSTR", "char *")
  typealias("LPBOOL", "int")

# int MultiByteToWideChar(
#   UINT CodePage,
#   DWORD dwFlags,
#   LPCSTR lpMultiByteStr,
#   int cbMultiByte,
#   LPWSTR lpWideCharStr,
#   int cchWideChar
# );

  extern "int MultiByteToWideChar(UINT,DWORD,LPCSTR,int,LPWSTR,int)"

# int WideCharToMultiByte(
#   UINT CodePage,
#   DWORD dwFlags,
#   LPCWSTR lpWideCharStr,
#   int cchWideChar,
#   LPSTR lpMultiByteStr,
#   int cbMultiByte,
#   LPCSTR lpDefaultChar,
#   LPBOOL lpUsedDefaultChar
# );

  extern "int WideCharToMultiByte(UINT,DWORD,LPCWSTR,int,LPSTR,int,LPCSTR,LPBOOL)"

  def MSEncode.mbtwc(code, str)
    rvalue = MSEncode.MultiByteToWideChar(code, 0, str, str.length, "", 0)
    raise if rvalue == 0
    res = "\x00" * rvalue
    rvalue = MSEncode.MultiByteToWideChar(code, 0, str, str.length, res, res.length)
    raise if rvalue == 0
    return res
  end

  def MSEncode.wctmb(code, str)
    rvalue = MSEncode.WideCharToMultiByte(code, 0, str, str.length, "", 0, 0, 0)
    if rvalue == 0
      DL::CFunc.win32_last_error
      raise 
    end
    res = "\x00" * rvalue
    rvalue = MSEncode.WideCharToMultiByte(code, 0, str, str.length, res, res.length, 0, 0)
    raise if rvalue == 0
    return res
  end

  def MSEncode.iconv(from, to, str)
    wc = MSEncode.mbtwc(from, str)
    mb = MSEncode.wctmb(to, wc)
    return mb
  end
end

puts MSEncode.iconv(932, 65001, "ほえほえふがふが")