Microsoft Store 版 Python で py2exe ビルドが WinError 87 で失敗する問題の根本原因調査と解決
WinError 87 (パラメーターが間違っています) の正体は、BeginUpdateResourceW が相対パスで ERROR_FILE_NOT_FOUND (2) を返すことだった — Microsoft Store Python (MSIX) 固有の問題を 7 Phase の体系的調査で特定した全記録
1. はじめに:問題の概要
本ドキュメントは、Microsoft Store 版 Python から作成した仮想環境で py2exe を使って .exe ファイルをビルドしようとすると、以下のエラーで失敗する問題について、7 Phase にわたる体系的な調査で根本原因を特定・解決した全記録である。
Building 'dist\get_mf_trans_monthly.exe'.
Traceback (most recent call last):
...
File "runtime.py", line 374, in build_exe
resource.add(type="PYTHONSCRIPT", name=1, value=script_info)
File "resources.py", line 49, in add
raise WindowsError(details) from None
OSError: [WinError 87] パラメーターが間違っています。
結論を先に述べる: WinError 87 は二次的なエラーであり、真の原因は BeginUpdateResourceW が相対パスで ERROR_FILE_NOT_FOUND (2) を返すことだった。Microsoft Store 版 Python (MSIX パッケージ) では、kernel32.BeginUpdateResourceW に相対パスを渡すと、CWD ではなく App Execution Alias 元のディレクトリに対してパスを解決するため、ファイルが見つからない。py2exe は BeginUpdateResourceW の戻り値(NULL)を検査せずに UpdateResourceW(NULL, ...) を呼び出し、WinError 87 が発生していた。
1.1. 環境情報
| 項目 | 問題のある環境 | 問題のない環境 |
|---|---|---|
| Python バージョン | 3.13.14 | 3.13.3 |
| Python インストール元 | Microsoft Store | python.org |
| sys.base_prefix | C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.13_... |
通常ディレクトリ |
| py2exe バージョン | 0.14.2.0 | 0.14.2.0 |
| OS | Windows 11 (Build 26200) | Windows 11 (Build 26200) |
| アーキテクチャ | 64-bit (AMD64) | 64-bit (AMD64) |
1.2. 解決策(先に知りたい方向け)
setup.py に以下の monkey-patch を追加するだけで解決する:
import os
from setuptools import setup
import py2exe
# Fix: Microsoft Store Python では BeginUpdateResourceW が相対パスを
# 正しく解決できない (ERROR_FILE_NOT_FOUND)。絶対パスに変換して回避する。
import py2exe._wapi as _wapi
_orig_BeginUpdateResourceW = _wapi.BeginUpdateResourceW
def _fixed_BeginUpdateResourceW(filename, delete_existing):
return _orig_BeginUpdateResourceW(os.path.abspath(filename), delete_existing)
_wapi.BeginUpdateResourceW = _fixed_BeginUpdateResourceW
py2exe.freeze(
console=['your_script.py'],
options={ ... },
)
2. 調査の全体像
以下の 7 Phase で体系的に原因を絞り込んだ。結果的に Phase 1 で得られた「UpdateResourceW の直接失敗」は偽陽性であり、Phase 6-7 で真の原因に到達した。
| Phase | 仮説 | 検証方法 | 結果 |
|---|---|---|---|
| 1 | UpdateResourceW API 自体が壊れている | ctypes で直接呼び出し | ❌ 偽陽性(テストの型宣言不備) |
| 2 | MS Store / Defender / CFA が原因 | 正しい型宣言で API テスト | ❌ 全テスト成功(API は正常) |
| 3 | py2exe のコード自体に問題がある | resources.py / runtime.py のソース取得 | ✅ コード確認完了 |
| 3b | _wapi の LPCWSTR 型変換が壊れている | 両環境で _wapi.py を比較 | ❌ 完全一致 |
| 4 | py2exe と同一コードパスの再現 | raw スタブで UpdateResources テスト | ✅ 成功(不思議) |
| 5 | get_runstub_bytes がスタブを加工して壊している | MD5 比較 | ❌ 完全一致(加工なし) |
| 6 | py2exe 内部の実行コンテキストが汚染されている | monkey-patch でパラメータ捕捉 | 🔑 BeginUpdateResourceW が NULL を返していた |
| 7 | NULL の原因は何か | GetLastError 取得 + パス変換テスト | 🎯 Error 2 (ファイル未発見) → 絶対パスで解決 |
3. Phase 1: 初期環境調査(investigate_winerror87.py)
3.1. 目的
問題環境の全体像を把握し、基本的な原因候補(ファイルロック、MS Store Python、ディスク容量、UpdateResourceW API 自体の動作)を一括で調査する読み取り専用の診断スクリプト。
3.2. 調査項目(10項目)
[1] ENVIRONMENT INFO - Python バージョン、MS Store 判定
[2] VIRTUAL ENVIRONMENT CHECK - venv 構成、sys.prefix / sys.base_prefix
[3] py2exe VERSION - バージョンとインストール場所
[4] setuptools VERSION - setuptools のバージョン
[5] dist/build DIRECTORIES - 既存ファイルとロック状態
[6] SCRIPT FILE SIZE - UpdateResource の 16MB 制限チェック
[7] ANTIVIRUS CHECK - Windows Defender の状態
[8] DISK SPACE - 空き容量と書き込み権限
[9] UpdateResource API TEST - UpdateResourceW を ctypes で直接呼び出し
[10] setup.py CONTENT - 設定内容の確認
3.3. 実行結果(問題環境)
[1] Python version : 3.13.14
MS Store Python?: False ← sys.executable のみ判定(不完全)
[2] sys.base_prefix : C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.13_...
↑ ここが MS Store を示している(見逃しポイント)
[5] exe locked?: NO (writable)
[9] UpdateResource FAILED: WinError 87 >>> THIS IS THE ROOT CAUSE
3.4. この時点での判断(後に覆る)
Section 9 で UpdateResourceW が直接失敗したことから、「API レベルの問題」と判断した。しかしこれは偽陽性だった。 テストスクリプトで ctypes.windll.kernel32.UpdateResourceW を argtypes を宣言せずに呼び出したため、整数パラメータ(10, 1)が LPCWSTR(ポインタ型)に正しく変換されず、API 呼び出し自体が不正だった。
教訓: ctypes で Windows API を呼び出す際は、必ず argtypes と restype を宣言する。宣言なしの呼び出しは引数の型変換が不正確になり、偽陽性の原因となる。
4. Phase 2: 正しい型宣言での API 再検証(investigate_winerror87_phase2.py)
4.1. 目的
Phase 1 の結果が ctypes の型宣言不備による偽陽性かどうかを検証する。正しい argtypes 宣言で UpdateResourceW を再テストし、MS Store / Defender / Controlled Folder Access の影響を切り分ける。
4.2. テスト構成
[0] MS Store Python の深い検出(sys.base_prefix)
[2] 正しい argtypes 宣言での UpdateResourceW テスト
[3] TEST A: py2exe スタブで UpdateResources(CWD 内)
[4] TEST B: cmd.exe コピーで UpdateResources
[5] TEST C: TEMP ディレクトリで UpdateResources
[6] Controlled Folder Access の設定確認
[7] PE ヘッダ解析(署名の有無)
4.3. 実行結果
[0] Base is MS Store: True
>>> CONFIRMED: venv was created from Microsoft Store Python
[3] TEST A (py2exe stub in CWD): result=1, last_error=0 ← 成功
[4] TEST B (cmd.exe copy): result=1, last_error=0 ← 成功
[5] TEST C (TEMP directory): result=1, last_error=0 ← 成功
[6] EnableControlledFolderAccess: 0 ← 無効
[7] PE: MZ OK, PE OK, x64, Not signed
4.4. 結論
全テスト成功。 Phase 1 の失敗は偽陽性(ctypes の型宣言不備)であり、UpdateResourceW API 自体はこの環境で正常に動作する。MS Store / Defender / CFA はいずれも原因ではない。
5. Phase 3 / 3b: py2exe ソースコードの取得と比較(investigate_winerror87_phase3.py, phase3b.py)
5.1. 目的
API は正常なのに py2exe が失敗する。py2exe 内部のコード(resources.py, runtime.py, _wapi.py)を直接読み取り、両環境で差異がないか比較する。
5.2. 取得した py2exe のコード(resources.py 核心部分)
# resources.py - UpdateResources コンテキストマネージャ
@contextlib.contextmanager
def UpdateResources(filename, *, delete_existing=False):
hrscr = _wapi.BeginUpdateResourceW(filename, delete_existing)
# ↑ 戻り値の NULL チェックがない!
resource_writer = ResourceWriter(hrscr, filename)
yield resource_writer
resource_writer.flush()
_wapi.EndUpdateResourceW(hrscr, False)
# resources.py - ResourceWriter.add (line 33-49)
def add(self, *, type, name, value, langid=0):
try:
_wapi.UpdateResourceW(self._hrscr,
_wapi.LPCWSTR(type),
_wapi.LPCWSTR(name),
langid,
value,
len(value))
except WindowsError as details:
raise WindowsError(details) from None # ← line 49: ここでエラー発生
# runtime.py - build_exe (line 340-374)
def build_exe(self, target, exe_path, libname):
exe_bytes = self.get_runstub_bytes(target)
with open(exe_path, "wb") as ofi:
ofi.write(exe_bytes) # ← スタブを dist/ に書き出し
script_data = self._create_script_data(target)
script_info = struct.pack("IIII", ...) + zippath + script_data
with UpdateResources(exe_path, delete_existing=True) as resource:
resource.add(type="PYTHONSCRIPT", name=1, value=script_info)
# ↑ ここで WinError 87 が発生
5.3. _wapi.py の比較結果(Phase 3b)
問題のある環境: _wapi.py ソースコード 126行
問題のない環境: _wapi.py ソースコード 126行
→ 完全一致
LPCWSTR('PYTHONSCRIPT'): 両環境とも c_wchar_p(...) → 正常
LPCWSTR(1): 両環境とも c_wchar_p(1) → 正常(MAKEINTRESOURCE)
LPCWSTR(0): 両環境とも c_wchar_p(None) → 正常
5.4. 結論
py2exe のコード・設定・型変換動作が両環境で完全に同一。 コードの差異は原因ではない。
6. Phase 4: py2exe と同一コードパスの再現(investigate_winerror87_phase4.py)
6.1. 目的
py2exe が内部で行う処理(スタブ書き出し → script_info 構築 → UpdateResources)を、py2exe の外で完全に再現して失敗するか検証する。
6.2. テストコード(核心部分)
# Step A: py2exe の runtime.py line 346-347 と同一
with open(stub, 'rb') as f:
exe_bytes = f.read()
with open(test_exe_1, "wb") as ofi:
ofi.write(exe_bytes)
# Step B: runtime.py line 360-365 と同一
script_data = open("get_mf_trans_monthly.py", "rb").read()
script_info = struct.pack("IIII", 0x78563412, 0, 0, len(script_data))
script_info += b"library.zip\0" + script_data + b"\0"
# Step C: runtime.py line 370-374 と同一
with UpdateResources(test_exe_1, delete_existing=True) as resource:
resource.add(type="PYTHONSCRIPT", name=1, value=script_info)
6.3. 実行結果(問題環境)
A: Writing 37376 bytes to __test_exact_1__.exe...
A: File written OK
B: script_info built: 10336 bytes
C: Calling UpdateResources(delete_existing=True)...
BeginUpdateResource succeeded
resource.add succeeded
C: SUCCESS! File size: 46592 bytes
6.4. 考察
py2exe と同一の API 呼び出しが、py2exe の外では成功する。 これにより「API の問題」「パラメータの問題」「スタブの問題」はすべて棄却された。問題は py2exe の実行コンテキスト内でのみ 発生する。
7. Phase 5: get_runstub_bytes の加工検証(investigate_winerror87_phase5.py)
7.1. 目的
Phase 4 のテストでは raw スタブを使ったが、py2exe は get_runstub_bytes() で加工したバイト列を書き出している可能性がある。加工による PE 構造の破損が原因かを検証する。
7.2. 検証方法
py2exe.freeze() を実際に実行(失敗は想定内)し、dist/ に書き出された exe のバイト列を raw スタブと比較する。
7.3. 実行結果
py2exe exe size : 37376 bytes
py2exe exe MD5 : dd788b56e742e549f8240ee2b36dbeca
Raw stub size : 37376 bytes
Raw stub MD5 : dd788b56e742e549f8240ee2b36dbeca
Sizes match : True
Content match : True
Content is IDENTICAL - get_runstub_bytes did not modify the stub
Test C (raw stub + compiled script_info):
Result: SUCCESS (50176 bytes)
7.4. 結論
exe のバイト列は完全一致。 get_runstub_bytes はスタブを加工していない。コンパイル済み .pyc を使った script_info でも成功する。
この時点の状況整理:
| 検証項目 | 結果 |
|---|---|
| API 自体の動作 | ✅ 正常(Phase 2) |
| py2exe のコード差異 | ❌ なし(Phase 3/3b) |
| 同一コードパスの再現 | ✅ 成功(Phase 4) |
| スタブの加工 | ❌ なし(Phase 5) |
| py2exe プロセス内での実行 | ❌ 失敗(一貫) |
問題は py2exe の実行中に何かが起きている。次の Phase で内部にインターセプトを仕掛ける。
8. Phase 6: py2exe 内部の monkey-patch(investigate_winerror87_phase6.py)— 転換点
8.1. 目的
py2exe の _wapi.BeginUpdateResourceW と _wapi.UpdateResourceW を monkey-patch し、py2exe が実際に呼び出す その瞬間 のパラメータ値を捕捉する。
8.2. monkey-patch コード(核心部分)
# 保存
_original_BeginUpdateResourceW = _wapi.BeginUpdateResourceW
def _debug_BeginUpdateResourceW(filename, delete_existing):
print(f" [INTERCEPT] BeginUpdateResourceW('{filename}', delete_existing={delete_existing})")
result = _original_BeginUpdateResourceW(filename, delete_existing)
print(f" [INTERCEPT] BeginUpdateResourceW returned: {result} (type: {type(result).__name__})")
return result
_wapi.BeginUpdateResourceW = _debug_BeginUpdateResourceW
def _debug_UpdateResourceW(hUpdate, lpType, lpName, wLanguage, lpData, cb):
print(f" hUpdate : {hUpdate} (type: {type(hUpdate).__name__})")
# ... パラメータ出力 ...
return _original_UpdateResourceW(hUpdate, lpType, lpName, wLanguage, lpData, cb)
_wapi.UpdateResourceW = _debug_UpdateResourceW
# monkey-patch 後に py2exe.freeze() を実行
py2exe.freeze(console=[...], options={...})
8.3. 実行結果 — 🔑 決定的な発見
Building 'dist\get_mf_trans_monthly.exe'.
[INTERCEPT] BeginUpdateResourceW('dist\get_mf_trans_monthly.exe', delete_existing=True)
[INTERCEPT] BeginUpdateResourceW returned: None (type: NoneType)
^^^^^^^^^^^^^^^^^^^^
↑ NULL ハンドルが返されている!
[INTERCEPT #1] UpdateResourceW called:
hUpdate : None (type: NoneType) ← NULL ハンドルで呼び出し
lpType : c_wchar_p(...)
lpName : c_wchar_p(1)
lpData : bytes, len=22753
Original FAILED: [WinError 87] パラメーターが間違っています。
Windows error code: 87
8.4. 根本原因への到達
BeginUpdateResourceW が None(NULL ハンドル)を返していた。 py2exe の resources.py は戻り値を検査せずに UpdateResourceW(None, ...) を呼び出し、WinError 87(パラメーター不正 = NULL ハンドル)で失敗していた。
WinError 87 は二次的なエラーであり、真の失敗は BeginUpdateResourceW にあった。
9. Phase 7: GetLastError の取得と修正(investigate_winerror87_phase7.py)— 完全解決
9.1. 目的
BeginUpdateResourceW がなぜ失敗するかの Windows エラーコードを取得し、修正を適用して検証する。
9.2. 検証コード(核心部分)
# 正しい use_last_error=True でエラーコードを保持
kernel32 = WinDLL("kernel32", use_last_error=True)
BeginUpdateResourceW = kernel32.BeginUpdateResourceW
BeginUpdateResourceW.restype = wintypes.HANDLE
BeginUpdateResourceW.argtypes = [wintypes.LPCWSTR, wintypes.BOOL]
# テスト1: 相対パス(py2exe が使うのと同じ)
h = BeginUpdateResourceW('dist\\test_relative.exe', True)
err = ctypes.get_last_error()
# テスト2: 絶対パス
h2 = BeginUpdateResourceW(os.path.abspath('dist\\test_relative.exe'), True)
err2 = ctypes.get_last_error()
9.3. 実行結果 — 🎯 根本原因確定
[1] REPRODUCE with RELATIVE path (like py2exe uses)
Calling BeginUpdateResourceW('dist\test_relative.exe', True)...
Handle: None, LastError: 2
RELATIVE path: FAILED! Error 2
Error message: 指定されたファイルが見つかりません。
Calling BeginUpdateResourceW('C:\prog\Python\_virtualenv\mf_account\dist\test_relative.exe', True)...
Handle: 2021067325448, LastError: 0
ABSOLUTE path: SUCCEEDED
相対パスでは ERROR_FILE_NOT_FOUND (2) で失敗し、絶対パスでは成功する。
9.4. タイミングテスト
[2] TIMING TESTS (write → delay → BeginUpdateResourceW)
delay= 0.0s: OK
delay= 0.1s: OK
delay= 3.0s: OK
※ これは絶対パスでのテストなのですべて成功。タイミング(Defender のスキャン遅延)は原因ではない。
9.5. monkey-patch による修正の適用と検証
[3] EXACT py2exe simulation with GetLastError
Running py2exe.freeze() with patched BeginUpdateResourceW...
Building 'dist\get_mf_trans_monthly.exe'.
[PATCH #1] BeginUpdateResourceW('dist\get_mf_trans_monthly.exe', True)
[PATCH] Handle: None, LastError: 2
[PATCH] FAILED: 指定されたファイルが見つかりません。
[PATCH] Retrying with absolute path: 'C:\prog\Python\_virtualenv\mf_account\dist\get_mf_trans_monthly.exe'
[PATCH] Retry handle: 2021067325448, LastError: 0
[PATCH] >>> ABSOLUTE PATH FIXED IT! Returning handle.
[PATCH #2] BeginUpdateResourceW('dist\get_mf_trans_monthly.exe', False)
[PATCH] Handle: None, LastError: 2
[PATCH] FAILED: 指定されたファイルが見つかりません。
[PATCH] Retrying with absolute path: ...
[PATCH] >>> ABSOLUTE PATH FIXED IT! Returning handle.
>>> py2exe.freeze() SUCCEEDED!
絶対パスへの変換で py2exe ビルドが完全に成功した。
10. 根本原因の分析
10.1. なぜ Microsoft Store 版 Python では相対パスが解決できないのか
Microsoft Store からインストールされた Python は、MSIX パッケージとして Windows の App Execution Alias 機構を通じて実行される。
# 問題のある環境の sys.base_prefix
C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.13_3.13.3824.0_x64__qbz5n2kfra8p0
通常の python.org 版 Python では、kernel32.BeginUpdateResourceW に渡された相対パスはプロセスの CWD(カレントワーキングディレクトリ)に対して解決される。しかし Microsoft Store 版 Python では、MSIX の仮想ファイルシステム層を経由するため、一部の Windows API(特に PE リソース操作系)がプロセスの CWD ではなく、パッケージのインストールディレクトリ(WindowsApps 配下)に対して相対パスを解決するケースがある。
結果として 'dist\get_mf_trans_monthly.exe' は以下のように解決される:
| Python の種類 | 相対パスの解決先 | 結果 |
|---|---|---|
| python.org 版 | C:\prog\...\mf_account\dist\get_mf_trans_monthly.exe |
✅ ファイルが存在 |
| MS Store 版 | C:\Program Files\WindowsApps\...\dist\get_mf_trans_monthly.exe |
❌ ファイルが存在しない |
10.2. なぜ通常のファイル操作(open(), os.path.exists())は動作するのか
Python の標準ファイル I/O(open(), os.*)は Python ランタイムが CWD を正しく管理しているため、相対パスが正常に解決される。問題が発生するのは ctypes 経由で直接呼び出される Windows API のうち、MSIX の仮想ファイルシステム層の影響を受けるもの に限られる。
10.3. py2exe 側のバグ
py2exe の resources.py には 2 つの問題がある:
-
BeginUpdateResourceWの戻り値チェックがないhrscr = _wapi.BeginUpdateResourceW(filename, delete_existing) # hrscr が None (NULL) の場合のエラー処理がない -
相対パスをそのまま Windows API に渡している
# runtime.py line 370 with UpdateResources(exe_path, delete_existing=True) as resource: # exe_path = 'dist\get_mf_trans_monthly.exe' (相対パス)
11. 解決策
11.1. 修正コード(setup.py)
import os
from setuptools import setup
import py2exe
# Fix: Microsoft Store Python では BeginUpdateResourceW が相対パスを
# 正しく解決できない (ERROR_FILE_NOT_FOUND)。絶対パスに変換して回避する。
import py2exe._wapi as _wapi
_orig_BeginUpdateResourceW = _wapi.BeginUpdateResourceW
def _fixed_BeginUpdateResourceW(filename, delete_existing):
return _orig_BeginUpdateResourceW(os.path.abspath(filename), delete_existing)
_wapi.BeginUpdateResourceW = _fixed_BeginUpdateResourceW
py2exe.freeze(
console=['get_mf_trans_monthly.py'],
options={
"includes": [ ... ],
"packages": [ ... ],
},
)
11.2. 代替案
- python.org 版 Python を使用する: MS Store 版ではなく、python.org 公式インストーラーの Python を使用すれば、この問題は発生しない。
- py2exe にパッチを提出する:
resources.pyのUpdateResources関数でos.path.abspath(filename)を適用し、BeginUpdateResourceWの戻り値を検査するよう修正する。
12. 調査の教訓
12.1. エラーの報告箇所 ≠ 真の失敗箇所
最も重要な教訓。WinError 87 は UpdateResourceW で報告されたが、真の原因は BeginUpdateResourceW にあった。
真の失敗箇所: BeginUpdateResourceW → NULL (ERROR_FILE_NOT_FOUND)
二次的エラー: UpdateResourceW(NULL, ...) → WinError 87 (Invalid Parameter)
報告されるエラー: WinError 87 ← ここだけを見ると誤診する
「パラメーターが不正」というエラーが出た場合、そのパラメータ(ハンドル等)を生成した上流の関数の戻り値を必ず検査せよ。
12.2. 偽陽性に注意
Phase 1 の UpdateResourceW 直接テストは argtypes 未宣言により偽陽性を生んだ。Phase 2 で正しい型宣言に修正して初めて API が正常動作することを確認できた。
検証コード自体にバグがあると、誤った根本原因に導かれる。 検証コードの正しさも検証が必要。
12.3. 「同一条件」の罠
Phase 4 で「py2exe と同一コードパス」のテストが成功したため、一時的に混乱した。しかし「同一」だったのはコードパスだけであり、ファイルパスの種類(相対 vs 絶対) が異なっていた。
Phase 4 テスト: os.path.join(dist_dir, "test.exe") → 絶対パス → 成功
py2exe 本体: "dist\get_mf_trans_monthly.exe" → 相対パス → 失敗
12.4. monkey-patch は最強のデバッグツール
Phase 6 の monkey-patch が最も効果的だった。py2exe のソースコードを変更せずに、実行時のパラメータを捕捉できた。ライブラリ内部のデバッグでは、ソース改変よりも monkey-patch が推奨される。
13. 診断スクリプト一覧
すべてのスクリプトは debug/ ディレクトリに保存されている。同様の問題に遭遇した場合、Phase 1 → Phase 7 の順に実行して原因を絞り込むことができる。
| ファイル名 | 行数 | 概要 |
|---|---|---|
investigate_winerror87.py |
272行 | Phase 1: 環境情報10項目の一括収集(Python版/MS Store判定/Defender/ディスク/UpdateResourceW直接テスト) |
investigate_winerror87_phase2.py |
355行 | Phase 2: 正しい ctypes 型宣言での API テスト。スタブ/cmd.exe/TEMP の3箇所 + CFA + PE ヘッダ解析 |
investigate_winerror87_phase3.py |
236行 | Phase 3: py2exe の resources.py / runtime.py ソースコードのダンプ + 段階的リソース追加テスト |
investigate_winerror87_phase3b.py |
202行 | Phase 3b: _wapi.py ソースダンプ + LPCWSTR 型変換テスト + 遅延あり/なし比較 |
investigate_winerror87_phase4.py |
221行 | Phase 4: py2exe と同一コードパスの完全再現(write → UpdateResources)+ 多段フォールバックテスト |
investigate_winerror87_phase5.py |
246行 | Phase 5: py2exe.freeze() 後の exe バイト列と raw スタブの MD5/PE ヘッダ比較 + compiled .pyc テスト |
investigate_winerror87_phase6.py |
212行 | Phase 6: _wapi.BeginUpdateResourceW / UpdateResourceW の monkey-patch によるパラメータ捕捉 |
investigate_winerror87_phase7.py |
225行 | Phase 7: BeginUpdateResourceW の GetLastError 取得 + 相対/絶対パス比較 + 自動修正の検証 |
14. まとめ
Microsoft Store 版 Python (MSIX パッケージ) は、python.org 版と比べて Windows API のファイルパス解決に差異がある。この差異は通常の Python コード(open(), os.*)では顕在化しないが、ctypes 経由で直接呼び出される kernel32 の API(今回は BeginUpdateResourceW)では相対パスの解決が CWD ではなくパッケージのインストールディレクトリに対して行われる場合がある。
この問題は py2exe に限らず、ctypes で Windows API を呼び出すすべての Python パッケージで発生する可能性がある。Windows API にファイルパスを渡す際は、常に os.path.abspath() で絶対パスに変換する ことを推奨する。