winregのREG_DWORDの扱いがちょっと

Python にはレジストリを扱うために winreg というライブラリがついてくるけれど、REG_DWORD の扱いに少し問題があることがわかった。

問題1:大きな整数が正しく読み込めない

例えば 4294967173 という大きな数を読もうとすると、-123 になってしまう。


# CPython 3.2.1 for Windows x86
from winreg import *
with OpenKey(HKEY_CURRENT_USER, r'Software\__test__') as key:
    data, typ = QueryValueEx(key, 'big_one')
    print(data)  # -123

DWORD 型というのは要するに unsigned long のことだ。しかし winreg の内部では、これを signed として扱っている箇所があるので、大きな数字を読み込もうとすると負数になってしまう現象が発生する。

問題2:大きな整数を書き込むと奇妙な場所で例外が出る

signed long に収まらない整数を書き込むと、いかなる値であっても全て 0xFFFFFFFF にされてしまう。さらに悪いことに、これ以降いくつかの関数で必ず不可解な例外が発生するようになる。

from winreg import *
with OpenKey(HKEY_CURRENT_USER, r'Software\__test__', access=KEY_SET_VALUE) as key:
    SetValueEx(key, 'big_one2', 0, REG_DWORD, 0x80000000)
try:
    max(1, 2, 3)  # something harmeless
except OverflowError:
    print('whoa!?')

ソースを眺めてみる限り、データが 0xFFFFFFFF になるのは、PyLong_AsLong() の戻り値 -1unsigned として解釈しているから。変な場所で例外が発生するのは、PyLong_AsLong() の例外を放置しているからだと思う。

というわけで

誰か英語が得意な人がいたら報告しておいてください。 報告されました。