使用-ldflags -H=windowsgui编译golang应用程序时,在命令窗口中打印输出

18

我有一个通常在后台静默运行的应用程序,因此我将其编译为

go build -ldflags -H=windowsgui <gofile>
为了在命令行上检查版本,我想通过将-V标志传递给命令行来获取包含版本信息的字符串并打印到命令提示符中,然后使应用程序退出。我添加了flag包和代码。当我使用它进行测试时,

go run <gofile> -V

它可以正常打印版本。但是当我编译exe文件时,它仅退出而不打印任何内容。我怀疑编译标志导致它无法访问控制台并将我的文本发送到了垃圾桶。

我尝试过多种方式打印到stderr和stdout,包括使用println、fprintf和os.stderr.write,但编译后的应用程序中没有任何输出。当使用那些标志编译时,我该如何在命令提示符中打印字符串?

5个回答

24

问题在于,当使用一个可执行文件创建进程时,该可执行文件的PE头中的"subsystem"变量设置为"Windows",该进程的三个标准句柄将关闭,并且它不与任何控制台相关联 - 无论您是否从控制台运行它。(实际上,如果您从非控制台运行其子系统设置为“console”的可执行文件,则会强制为该进程创建控制台并将进程附加到它 - 您通常会看到突然弹出的控制台窗口。)

因此,在Windows上从GUI进程向控制台打印任何内容,您必须明确连接该进程到其父进程附加的控制台(如果有),例如在这里解释的那样。要做到这一点,您可以调用AttachConsole API函数。使用Go,可以使用syscall包来完成这个操作:
package main

import (
    "fmt"
    "syscall"
)

const (
    ATTACH_PARENT_PROCESS = ^uint32(0) // (DWORD)-1
)

var (
    modkernel32 = syscall.NewLazyDLL("kernel32.dll")

    procAttachConsole = modkernel32.NewProc("AttachConsole")

)

func AttachConsole(dwParentProcess uint32) (ok bool) {
    r0, _, _ := syscall.Syscall(procAttachConsole.Addr(), 1, uintptr(dwParentProcess), 0, 0)
    ok = bool(r0 != 0)
    return
}

func main() {
    ok := AttachConsole(ATTACH_PARENT_PROCESS)
    if ok {
        fmt.Println("Okay, attached")
    }
}

为了真正完整,当AttachConsole()失败时,这段代码可能需要采取以下两种路线之一:
  • 调用AllocConsole()以创建自己的控制台窗口。

    我认为这对于显示版本信息几乎没有用处,因为进程通常在打印后退出,结果用户体验将是一个控制台窗口弹出并立即消失;高级用户会得到提示,他们应该从控制台重新运行应用程序,但普通用户可能无法处理。

  • 发布显示相同信息的GUI对话框。

    我认为这正是所需的:请注意,响应用户指定某些命令行参数显示帮助/使用消息往往与控制台有心理关联,但这不是必须遵循的教条:例如,请尝试在控制台上运行msiexec.exe /?,看看会发生什么。


4

这里已经发布的解决方案存在一个问题,就是它们将所有输出都重定向到控制台,因此如果我运行 ./myprogram >file,那么输出就会丢失。我编写了一个新模块,github.com/apenwarr/fixconsole,可以避免这个问题。你可以像这样使用它:

import (
        "fmt"
        "github.com/apenwarr/fixconsole"
        "os"
)

func main() {
        err := fixconsole.FixConsoleIfNeeded()
        if err != nil {
                fmt.Fatalf("FixConsoleOutput: %v\n", err)
        }
        os.Stdout.WriteString(fmt.Sprintf("Hello stdout\n"))
        os.Stderr.WriteString(fmt.Sprintf("Hello stderr\n"))
}

3

上面的回答很有帮助,但是它并没有直接为我解决问题。经过进一步的研究,我找到了以下代码:

// go build -ldflags -H=windowsgui
package main

import "fmt"
import "os"
import "syscall"

func main() {
    modkernel32 := syscall.NewLazyDLL("kernel32.dll")
    procAllocConsole := modkernel32.NewProc("AllocConsole")
    r0, r1, err0 := syscall.Syscall(procAllocConsole.Addr(), 0, 0, 0, 0)
    if r0 == 0 { // Allocation failed, probably process already has a console
        fmt.Printf("Could not allocate console: %s. Check build flags..", err0)
        os.Exit(1)
    }
    hout, err1 := syscall.GetStdHandle(syscall.STD_OUTPUT_HANDLE)
    hin, err2 := syscall.GetStdHandle(syscall.STD_INPUT_HANDLE)
    if err1 != nil || err2 != nil { // nowhere to print the error
        os.Exit(2)
    }
    os.Stdout = os.NewFile(uintptr(hout), "/dev/stdout")
    os.Stdin = os.NewFile(uintptr(hin), "/dev/stdin")
    fmt.Printf("Hello!\nResult of console allocation: ")
    fmt.Printf("r0=%d,r1=%d,err=%s\nFor Goodbye press Enter..", r0, r1, err0)
    var s string
    fmt.Scanln(&s)
    os.Exit(0)
}

重点:在分配/连接控制台之后,需要获取stdout句柄,使用此句柄打开文件并将其分配给os.Stdout变量。如果您需要stdin,则必须为stdin重复相同的操作。

2
您可以在不使用-H=windowsgui的情况下获得所需的行为;基本上,您将创建一个标准应用程序(具有自己的控制台窗口),并在程序退出之前隐藏它。
func Console(show bool) {
    var getWin = syscall.NewLazyDLL("kernel32.dll").NewProc("GetConsoleWindow")
    var showWin = syscall.NewLazyDLL("user32.dll").NewProc("ShowWindow")
    hwnd, _, _ := getWin.Call()
    if hwnd == 0 {
            return
    }
    if show {
       var SW_RESTORE uintptr = 9
       showWin.Call(hwnd, SW_RESTORE)
    } else {
       var SW_HIDE uintptr = 0
       showWin.Call(hwnd, SW_HIDE)
    }
}

然后像这样使用它:

func main() {
    Console(false)
    defer Console(true)
    ...
    fmt.Println("Hello World")
    ...
}

0
如果您构建一个无窗口应用程序,您可以使用PowerShell命令Out-String获取输出。
.\\main.exe | out-string

你的构建命令可能如下:

cls; go build -i -ldflags -H=windowsgui main.go; .\\main.exe | out-string;

或者

cls; go run -ldflags -H=windowsgui main.go | out-string

不需要复杂的系统调用或内核动态链接库!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接