import t, c from stdint import * import platmacro import memhub import string # Windows 后端依赖 import w32.win32base import w32.win32file import w32.win32process import w32.win32sync # POSIX 后端依赖 import posix # 子进程输出缓冲区大小 OUTPUT_BUF_SIZE: t.CDefine = 65536 # 模块级全局 MBuddy 指针(由用户在 main 中赋值初始化) _mbuddy: memhub.MemBuddy | t.CPtr # ============================================================================= # CompletedProcess 结果类 # ============================================================================= class CompletedProcess: """subprocess.run 的返回结果""" args: str # 原始命令行 returncode: int # 进程退出码(0 表示成功) stdout: str # 标准输出(capture_output=True 时) stderr: str # 标准错误(capture_output=True 时) def __new__(self, args: str, returncode: int, stdout: str, stderr: str) -> t.CPtr: # 堆分配(避免返回栈地址导致 use-after-return) return t.CPtr(_mbuddy.alloc(CompletedProcess.__sizeof__())) def __init__(self, args: str, returncode: int, stdout: str, stderr: str): self.args = args self.returncode = returncode self.stdout = stdout self.stderr = stderr # ============================================================================= # 入口函数 # ============================================================================= def run(args: str, capture_output: bool = True, text: bool = True) -> CompletedProcess | t.CPtr: """同步执行子进程并等待完成。 args 为完整命令行字符串。 capture_output=True 时捕获 stdout/stderr。 text=True 时输出为字符串(当前实现总是返回字符串)。 返回 CompletedProcess 对象。""" if platmacro.IS_WINDOWS: return _run_win32(args, capture_output, text) return _run_posix(args, capture_output, text) # ============================================================================= # Windows 后端实现 # ============================================================================= def _run_win32(args: str, capture_output: bool, text: bool) -> CompletedProcess | t.CPtr: """Windows 平台子进程执行(CreateProcessA + 管道)""" # 复制命令行(CreateProcessA 需要可修改的 lpCommandLine) args_len: t.CSizeT = len(args) cmd_line: bytes = _mbuddy.alloc(args_len + 1) if cmd_line == None: return CompletedProcess(args, -1, None, None) i: t.CSizeT = 0 for ch in args: cmd_line[i] = ch i += 1 cmd_line[args_len] = 0 # 安全属性(允许句柄继承) sa: w32.win32base.SECURITY_ATTRIBUTES = w32.win32base.SECURITY_ATTRIBUTES() sa.nLength = w32.win32base.SECURITY_ATTRIBUTES.__sizeof__() sa.lpSecurityDescriptor = t.CPtr(0) sa.bInheritHandle = 1 # TRUE # 创建 stdout 管道 stdout_read: w32.win32base.HANDLE = t.CPtr(0) stdout_write: w32.win32base.HANDLE = t.CPtr(0) if capture_output: pipe_result: BOOL = w32.win32file.CreatePipe( t.CPtr(c.Addr(stdout_read)), t.CPtr(c.Addr(stdout_write)), t.CPtr(c.Addr(sa)), 0 ) if pipe_result == 0: return CompletedProcess(args, -1, None, None) # 防止读端被子进程继承 w32.win32file.SetHandleInformation(stdout_read, w32.win32file.HANDLE_FLAG_INHERIT, 0) # 设置启动信息 si: w32.win32process.STARTUPINFOA = w32.win32process.STARTUPINFOA() si.cb = w32.win32process.STARTUPINFOA.__sizeof__() if capture_output: si.dwFlags = w32.win32process.STARTF_USESTDHANDLES si.hStdOutput = stdout_write si.hStdError = stdout_write # stderr 合并到 stdout pi: w32.win32process.PROCESS_INFORMATION = w32.win32process.PROCESS_INFORMATION() # 创建进程 # capture_output=True 时用 CREATE_NO_WINDOW(输出到管道,不需要控制台) # capture_output=False 时用 0(继承父进程控制台,让 stdout 直接显示) create_flags: ULONG = w32.win32process.CREATE_NO_WINDOW if capture_output else 0 create_result: BOOL = w32.win32process.CreateProcessA( t.CPtr(0), # lpApplicationName (NULL,从命令行解析) cmd_line, # lpCommandLine (可修改) t.CPtr(0), # lpProcessAttributes t.CPtr(0), # lpThreadAttributes 1, # bInheritHandles = TRUE create_flags, # dwCreationFlags t.CPtr(0), # lpEnvironment (继承父进程) t.CPtr(0), # lpCurrentDirectory (继承父进程) t.CPtr(c.Addr(si)), t.CPtr(c.Addr(pi)) ) if create_result == 0: # 创建失败 if capture_output: w32.win32base.CloseHandle(stdout_read) w32.win32base.CloseHandle(stdout_write) return CompletedProcess(args, -1, None, None) # 关闭父进程的写端(子进程已有写端的继承副本) if capture_output: w32.win32base.CloseHandle(stdout_write) # 读取 stdout + 等待进程结束 # 注意:必须边读边等,否则子进程输出超过管道缓冲区时会死锁 # (子进程阻塞在写 stdout,父进程阻塞在 WaitForSingleObject) stdout_str: str = None if capture_output: buf_size: ULONG = OUTPUT_BUF_SIZE buf: bytes = _mbuddy.alloc(buf_size) if buf != None: total: ULONG = 0 while 1: bytes_read: ULONG = 0 read_result: BOOL = w32.win32file.ReadFile( stdout_read, buf + total, buf_size - 1 - total, t.CPtr(c.Addr(bytes_read)), t.CPtr(0) ) if read_result == 0 or bytes_read == 0: break total += bytes_read # 缓冲区将满,扩容(翻倍) if total >= buf_size - 1 - 4096: new_size: ULONG = buf_size * 2 new_buf: bytes = _mbuddy.alloc(new_size) if new_buf != None: string.memcpy(new_buf, buf, total) buf = new_buf buf_size = new_size else: # 扩容失败,停止读取 break buf[total] = 0 stdout_str = str(buf) # 等待进程结束(如果还没退出的话) w32.win32sync.WaitForSingleObject(pi.hProcess, w32.win32base.INFINITE) # 获取退出码 exit_code: ULONG = 0 w32.win32process.GetExitCodeProcess(pi.hProcess, t.CPtr(c.Addr(exit_code))) # 关闭句柄 w32.win32base.CloseHandle(pi.hProcess) w32.win32base.CloseHandle(pi.hThread) if capture_output: w32.win32base.CloseHandle(stdout_read) return CompletedProcess(args, exit_code, stdout_str, None) # ============================================================================= # POSIX 后端实现 # ============================================================================= def _run_posix(args: str, capture_output: bool, text: bool) -> CompletedProcess | t.CPtr: """POSIX 平台子进程执行(fork + execvp + pipe + waitpid) 注意:当前为桩函数。POSIX 实现需要 pipe/fork/waitpid, 这些函数在 Windows MinGW 上不可用,待 POSIX 平台测试时再完善。""" return CompletedProcess(args, -1, None, None)