Python2.7の_winregでは読めない文字がある

_winreg にある関数に REG_SZ 値を取得させると、 unicode を返すようになっている。でも、内部では ANSI 版の Widnows API を使っているので、cp932(≒Shift_JIS)に含まれない文字が化ける。例えば、韓国語のハングルや、中国語の簡体字を日本語版の Windows で読ませるとこの通り。

import _winreg
with _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, r'Software\_pytest') as key:
    print _winreg.QueryValueEx(key, 'hangul')[0]  # ??
    print _winreg.QueryValueEx(key, 'hanzi')[0]   # ?体字

一部が ? になってしまっている。

書くほうはどうかというと、

import _winreg
with _winreg.OpenKeyEx(_winreg.HKEY_CURRENT_USER,
                       r'Software\_pytest',
                       0, _winreg.KEY_WRITE) as key:
    # はてなダイアリーは未だにEUC-JPなので適当にエスケープ
    data = u'\ud55c\uae00 and \u7b80\u4f53\u5b57'
    _winreg.SetValueEx(key, "mix", 0, _winreg.REG_SZ, data)

こちらも一部が ? になってしまう。

ANSI 版の Windows API は大抵 UNICODE 版のラッパーなので _winreg を使うと2回も文字コードを変換していることになって効率が悪いし、扱える文字の幅を意味もなく制限していることになると思う。

回避策

まず PyWin32 を試した。

import win32api
import win32con
handle = win32api.RegOpenKeyEx(
    win32con.HKEY_CURRENT_USER,
    r"Software\_pytest",
    0,
    win32con.KEY_READ)
print type(win32api.RegQueryValueEx(handle, u'hangul')[0])  # <type 'str'>
handle.close()

戻り値が str なので駄目だ。

WMI (Windows Management Instrumentation) 経由でレジストリが読めるというのを聞いたことがある。ちょうどここにライブラリがある(動作には PyWin32 が必要)。やってみるときちんと読める。

import wmi
HKEY_CURRENT_USER = 0x80000001
conn = wmi.WMI()
err, value = conn.StdRegProv.GetStringValue(
    HKEY_CURRENT_USER, r"Software\_pytest", "hanzi")
if not err:
    print value.encode('cp932', 'xmlcharrefreplace')  # &#31616;体字

ここまで来て気付いたのだが、コードページが 932 なコマンドプロンプトにハングルと簡体字は表示できないな。上のコードでは適当に置き換えておいた。

次、ctypes 。 RegQueryValueExW() でワイド文字列を読むのは面倒くさいので、Vista から使えるようになった RegGetValueW() を使うことにする。

import ctypes
from ctypes import wintypes

HKEY_CURRENT_USER = 0x80000001
RRF_RT_REG_SZ = 0x00000002
ERROR_SUCCESS = 0L

buf = ctypes.create_unicode_buffer(32)
size = wintypes.DWORD(ctypes.sizeof(buf))  # この sizeof はバイト数を返す

RegGetValue = ctypes.windll.Advapi32.RegGetValueW
RegGetValue.restype = wintypes.LONG
ret = RegGetValue(HKEY_CURRENT_USER, ur'Software\_pytest',
                  u'hanzi', RRF_RT_REG_SZ, None, buf, ctypes.byref(size))
if ret == ERROR_SUCCESS:
    print buf.value.encode('cp932', 'xmlcharrefreplace')  # &#31616;体字

問題なし。

Python 3.x の winreg では、ここに書いたような問題は起きないはず。UNICODE 版の Windows API を使っているからね。早く 3.x に移行したいなあ。