Files
TransPyC/includes/subprocess.py
2026-07-18 19:25:40 +08:00

197 lines
7.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)