You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用CreateProcessWithTokenW在Go中重定向输出到当前控制台

问题:使用CreateProcessWithTokenW启动CMD时无法重定向到当前控制台

我正在用Go语言尝试通过CreateProcessWithTokenW启动新的CMD shell,目标是将标准输出重定向到当前控制台窗口,让命令在同一窗口执行。但当前代码会打开独立的新CMD窗口,无法实现预期效果;而用exec.Command的代码可以正常将输出保留在同一控制台。

现有问题代码:

func launchCmd(token windows.Token) {
    var si windows.StartupInfo
    var pi windows.ProcessInformation

    // Duplicate the handles
    dupStdin, err := duplicateHandle(os.Stdin.Fd())
    if err != nil {
        fmt.Println("Error duplicating stdin handle:", err)
        return
    }
    dupStdout, err := duplicateHandle(os.Stdout.Fd())
    if err != nil {
        fmt.Println("Error duplicating stdout handle:", err)
        return
    }
    dupStderr, err := duplicateHandle(os.Stderr.Fd())
    if err != nil {
        fmt.Println("Error duplicating stderr handle:", err)
        return
    }

    defer windows.CloseHandle(dupStdin)
    defer windows.CloseHandle(dupStdout)
    defer windows.CloseHandle(dupStderr)

    si = windows.StartupInfo{
        Cb:        uint32(unsafe.Sizeof(si)),
        Flags:     windows.STARTF_USESTDHANDLES,
        StdInput:  dupStdin,
        StdOutput: dupStdout,
        StdErr:    dupStderr,
    }

    err = CreateProcessWithTokenW(
        token,
        0,
        "C:\\Windows\\System32\\cmd.exe",
        &si,
        &pi,
    )
    if err != nil {
        fmt.Println("Error CreateProcessWithTokenW:", err)
        return
    }

    defer windows.CloseHandle(pi.Process)
    defer windows.CloseHandle(pi.Thread)

    fmt.Printf("Launched cmd.exe with PID: %d\n", pi.ProcessId)
    windows.WaitForSingleObject(pi.Process, windows.INFINITE)
    fmt.Println("Process completed.")
}

func duplicateHandle(fd uintptr) (windows.Handle, error) {
    var dupHandle windows.Handle
    procHandle := windows.CurrentProcess()
    err := windows.DuplicateHandle(procHandle, windows.Handle(fd), procHandle, &dupHandle, 0, true, windows.DUPLICATE_SAME_ACCESS)
    if err != nil {
        return 0, fmt.Errorf("error duplicating handle: %w", err)
    }
    return dupHandle, nil
}

func CreateProcessWithTokenW(duplicatedToken windows.Token, creationFlags uint32, commandLine string, startupInfo *windows.StartupInfo, processInfo *windows.ProcessInformation) error {
    cmdPtr, err := syscall.UTF16PtrFromString(commandLine)
    if err != nil {
        return err
    }

    r1, _, e1 := syscall.SyscallN(
        procCreateProcessWithTokenW.Addr(),
        uintptr(duplicatedToken),
        0,
        0,
        uintptr(unsafe.Pointer(cmdPtr)),
        uintptr(creationFlags),
        0,
        0,
        uintptr(unsafe.Pointer(startupInfo)),
        uintptr(unsafe.Pointer(processInfo)),
    )
    if r1 == 0 {
        return e1
    }
    return nil
}

对比可行的exec.Command代码:

cmd := exec.Command("cmd.exe")
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

解决方案

要实现和exec.Command相同的控制台重定向效果,需要修改以下几点:

1. 正确获取标准输入输出的Windows句柄

不要使用os.Stdin.Fd(),而是调用Windows API的GetStdHandle获取原生控制台句柄,避免Go的文件描述符包装带来的问题。

2. 调整StartupInfo的标志和窗口显示设置

添加STARTF_USESHOWWINDOW标志,并设置wShowWindow为SW_SHOW,确保子进程复用当前控制台窗口而非创建新窗口。

3. 修正CreateProcessWithTokenW的参数和加载

确保正确加载CreateProcessWithTokenW函数,并设置合适的创建标志(如CREATE_UNICODE_ENVIRONMENT)。

修改后的完整代码:

package main

import (
    "fmt"
    "syscall"
    "unsafe"

    "golang.org/x/sys/windows"
)

var procCreateProcessWithTokenW = windows.NewLazySystemDLL("advapi32.dll").NewProc("CreateProcessWithTokenW")

func launchCmd(token windows.Token) {
    var si windows.StartupInfo
    var pi windows.ProcessInformation

    // 获取标准输入输出的原生Windows句柄
    stdinHandle := windows.GetStdHandle(windows.STD_INPUT_HANDLE)
    stdoutHandle := windows.GetStdHandle(windows.STD_OUTPUT_HANDLE)
    stderrHandle := windows.GetStdHandle(windows.STD_ERROR_HANDLE)

    // 复制句柄
    dupStdin, err := duplicateHandle(stdinHandle)
    if err != nil {
        fmt.Println("Error duplicating stdin handle:", err)
        return
    }
    dupStdout, err := duplicateHandle(stdoutHandle)
    if err != nil {
        fmt.Println("Error duplicating stdout handle:", err)
        return
    }
    dupStderr, err := duplicateHandle(stderrHandle)
    if err != nil {
        fmt.Println("Error duplicating stderr handle:", err)
        return
    }

    defer windows.CloseHandle(dupStdin)
    defer windows.CloseHandle(dupStdout)
    defer windows.CloseHandle(dupStderr)

    // 配置StartupInfo:同时使用标准句柄和窗口显示设置
    si = windows.StartupInfo{
        Cb:          uint32(unsafe.Sizeof(si)),
        Flags:       windows.STARTF_USESTDHANDLES | windows.STARTF_USESHOWWINDOW,
        StdInput:    dupStdin,
        StdOutput:   dupStdout,
        StdErr:      dupStderr,
        ShowWindow:  windows.SW_SHOW, // 复用当前控制台窗口
    }

    // 设置创建标志:使用Unicode环境
    creationFlags := uint32(windows.CREATE_UNICODE_ENVIRONMENT)

    err = CreateProcessWithTokenW(
        token,
        0,
        "C:\\Windows\\System32\\cmd.exe",
        creationFlags,
        &si,
        &pi,
    )
    if err != nil {
        fmt.Println("Error CreateProcessWithTokenW:", err)
        return
    }

    defer windows.CloseHandle(pi.Process)
    defer windows.CloseHandle(pi.Thread)

    fmt.Printf("Launched cmd.exe with PID: %d\n", pi.ProcessId)
    windows.WaitForSingleObject(pi.Process, windows.INFINITE)
    fmt.Println("Process completed.")
}

// 修改参数为windows.Handle,直接接收原生句柄
func duplicateHandle(srcHandle windows.Handle) (windows.Handle, error) {
    var dupHandle windows.Handle
    procHandle := windows.CurrentProcess()
    // 确保复制时允许子进程继承句柄
    err := windows.DuplicateHandle(procHandle, srcHandle, procHandle, &dupHandle, 0, true, windows.DUPLICATE_SAME_ACCESS)
    if err != nil {
        return 0, fmt.Errorf("error duplicating handle: %w", err)
    }
    return dupHandle, nil
}

// 调整函数参数顺序,加入creationFlags的正确传递
func CreateProcessWithTokenW(duplicatedToken windows.Token, logonFlags uint32, commandLine string, creationFlags uint32, startupInfo *windows.StartupInfo, processInfo *windows.ProcessInformation) error {
    cmdPtr, err := syscall.UTF16PtrFromString(commandLine)
    if err != nil {
        return err
    }

    r1, _, e1 := syscall.SyscallN(
        procCreateProcessWithTokenW.Addr(),
        uintptr(duplicatedToken),       // hToken
        uintptr(logonFlags),            // dwLogonFlags
        uintptr(0),                     // lpApplicationName (设为nil,使用commandLine)
        uintptr(unsafe.Pointer(cmdPtr)),// lpCommandLine
        uintptr(creationFlags),         // dwCreationFlags
        uintptr(0),                     // lpEnvironment (继承父进程环境)
        uintptr(0),                     // lpCurrentDirectory (继承父进程目录)
        uintptr(unsafe.Pointer(startupInfo)), // lpStartupInfo
        uintptr(unsafe.Pointer(processInfo)), // lpProcessInformation
    )
    if r1 == 0 {
        return e1
    }
    return nil
}

内容的提问来源于stack exchange,提问作者OhGodWhy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 20:35:55