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.UpdateResourceWargtypes を宣言せずに呼び出したため、整数パラメータ(10, 1)が LPCWSTR(ポインタ型)に正しく変換されず、API 呼び出し自体が不正だった。

教訓: ctypes で Windows API を呼び出す際は、必ず argtypesrestype を宣言する。宣言なしの呼び出しは引数の型変換が不正確になり、偽陽性の原因となる。


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. 根本原因への到達

BeginUpdateResourceWNone(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 つの問題がある:

  1. BeginUpdateResourceW の戻り値チェックがない

    hrscr = _wapi.BeginUpdateResourceW(filename, delete_existing)
    # hrscr が None (NULL) の場合のエラー処理がない
    
  2. 相対パスをそのまま 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. 代替案


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() で絶対パスに変換する ことを推奨する。