PyInstaller生成exe自动更新遇WinError32文件占用问题求助
解决PyInstaller生成EXE更新时文件被占用的问题
出现WinError 32的核心原因是:主进程file1.exe还在运行(或未完全释放文件句柄)时,updateloader.exe尝试覆盖它——Windows系统不允许修改正在被进程占用的可执行文件。下面是几种可行的解决方法:
方法1:让更新器等待主进程完全退出
主进程启动更新器时,把自身PID传递给更新器,更新器等待该PID对应的进程彻底结束后,再执行复制操作。这种方法最可靠,无需依赖不确定的延迟时间。
主进程(file1.exe)代码修改:
import atexit import subprocess import os def launch_updater(): updater_path = os.path.join(os.path.dirname(__file__), "updateloader.exe") # 将当前进程PID作为参数传给更新器 subprocess.Popen([updater_path, str(os.getpid())]) # 注册退出时启动更新器 atexit.register(launch_updater)
更新器(updateloader.exe)代码:
如果不想额外依赖第三方库,用Windows原生API实现进程等待:
import sys import ctypes from ctypes import wintypes import shutil kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) SYNCHRONIZE = 0x00100000 INFINITE = -1 # 定义Windows API参数类型 kernel32.OpenProcess.argtypes = [wintypes.DWORD, wintypes.BOOL, wintypes.DWORD] kernel32.OpenProcess.restype = wintypes.HANDLE kernel32.WaitForSingleObject.argtypes = [wintypes.HANDLE, wintypes.DWORD] kernel32.WaitForSingleObject.restype = wintypes.DWORD kernel32.CloseHandle.argtypes = [wintypes.HANDLE] def wait_for_process_exit(pid): # 打开目标进程获取句柄 h_process = kernel32.OpenProcess(SYNCHRONIZE, False, pid) if not h_process: return # 等待进程退出 kernel32.WaitForSingleObject(h_process, INFINITE) # 关闭句柄 kernel32.CloseHandle(h_process) if __name__ == "__main__": if len(sys.argv) < 2: sys.exit(1) parent_pid = int(sys.argv[1]) wait_for_process_exit(parent_pid) # 执行更新操作 src = "C:\\Users\\xy\\Downloads\\file1.exe" dst = "C:\\Users\\xy\\folder\\file1.exe" try: shutil.copy2(src, dst) print("更新完成") except Exception as e: print(f"更新失败: {str(e)}")
如果允许使用第三方库,用psutil会更简洁:
import sys import psutil import shutil import time def wait_for_process_exit(pid): try: proc = psutil.Process(pid) while proc.is_running(): time.sleep(0.5) except psutil.NoSuchProcess: pass if __name__ == "__main__": if len(sys.argv) < 2: sys.exit(1) parent_pid = int(sys.argv[1]) wait_for_process_exit(parent_pid) src = "C:\\Users\\xy\\Downloads\\file1.exe" dst = "C:\\Users\\xy\\folder\\file1.exe" try: shutil.copy2(src, dst) print("更新完成") except Exception as e: print(f"更新失败: {str(e)}")
打包updateloader.exe时,记得把psutil包含进去(如果用这个版本)。
方法2:重命名旧文件再替换
Windows允许重命名正在运行的可执行文件,我们可以先把旧的file1.exe重命名为临时文件,再复制新文件到目标路径,下次启动新程序时删除临时文件。
更新器(updateloader.exe)代码:
import os import shutil import sys def main(): src = "C:\\Users\\xy\\Downloads\\file1.exe" dst = "C:\\Users\\xy\\folder\\file1.exe" old_file_path = dst + ".old" # 重命名旧文件 if os.path.exists(dst): try: os.rename(dst, old_file_path) except Exception as e: print(f"无法重命名旧文件: {str(e)}") sys.exit(1) # 复制新文件 try: shutil.copy2(src, dst) print("更新完成") except Exception as e: # 复制失败,恢复旧文件 if os.path.exists(old_file_path): os.rename(old_file_path, dst) print(f"更新失败: {str(e)}") sys.exit(1) if __name__ == "__main__": main()
主进程(file1.exe)启动时清理旧文件:
在file1.exe的初始化代码中加入:
import os def cleanup_old_version(): old_file_path = os.path.join(os.path.dirname(__file__), "file1.exe.old") if os.path.exists(old_file_path): try: os.remove(old_file_path) except: # 清理失败不影响主程序运行 pass # 启动时执行清理 cleanup_old_version()
方法3:主程序主动退出后启动更新器
如果用户确认更新,主程序先启动更新器,然后立即退出,更新器短暂等待后执行替换。这种方法简单但依赖等待时间,可能存在不确定性。
主进程(file1.exe)代码:
import subprocess import sys import os import tkinter.messagebox as msgbox def check_and_update(): # 这里是你的版本检查逻辑,假设已确认需要更新 if msgbox.askyesno("更新提示", "发现新版本,是否更新?"): updater_path = os.path.join(os.path.dirname(__file__), "updateloader.exe") subprocess.Popen([updater_path]) # 立即退出主程序 sys.exit(0)
更新器(updateloader.exe)代码:
import time import shutil # 等待3秒,确保主进程完全退出 time.sleep(3) src = "C:\\Users\\xy\\Downloads\\file1.exe" dst = "C:\\Users\\xy\\folder\\file1.exe" try: shutil.copy2(src, dst) print("更新完成") except Exception as e: print(f"更新失败: {str(e)}")
内容的提问来源于stack exchange,提问作者Fab
相关产品推荐
相关产品推荐

