Printing output to a command window when golang application is compiled with -ldflags -H=windowsgui

╄→гoц情女王★ 提交于 2021-02-17 19:09:29

问题


I have an application that usually runs silent in the background, so I compile it with

go build -ldflags -H=windowsgui <gofile>

To check the version at the command line, I wanted to pass a -V flag to the command line to get the string holding the version to be printed to the command prompt then have the application exit. I added the flag package and code. When I test it with

go run <gofile> -V

...it prints the version fine. When I compile the exe, it just exits, printing nothing. I suspect it's the compilation flag causing it to not access the console and sending my text into the bit bucket.

I've tried variations to print to stderr and stdout, using println and fprintf and os.stderr.write, but nothing appears from the compiled application. How should I try printing a string to the command prompt when compiled with those flags?


回答1:


The problem is that when a process is created using an executable which has the "subsystem" variable in its PE header set to "Windows", the process has its three standard handles closed and it is not associated with any console—no matter if you run it from the console or not. (In fact, if you run an executable which has its subsystem set to "console" not from a console, a console is forcibly created for that process and the process is attached to it—you usually see it as a console window popping up all of a sudden.)

Hence, to print anything to the console from a GUI process on Windows you have to explicitly connect that process to the console which is attached to its parent process (if it has one), like explained here for instance. To do this, you call the AttachConsole API function. With Go, this can be done using the syscall package:

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")
    }
}

To be truly complete, when AttachConsole() fails, this code should probably take one of these two routes:

  • Call AllocConsole() to get its own console window created for it.

    It'd say this is pretty much useless for displaying version information as the process usually quits after printing it, and the resulting user experience will be a console window popping up and immediately disappearing; power users will get a hint that they should re-run the application from the console but mere mortals won't probably cope.

  • Post a GUI dialog displaying the same information.

    I think this is just what's needed: note that displaying help/usage messages in response to the user specifying some command-line argument is quite often mentally associated with the console, but this is not a dogma to follow: for instance, try running msiexec.exe /? at the console and see what happens.




回答2:


Answer above was helpful but alas it did not work for me out of the box. After some additional research I came to this code:

// 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)
}

The key point: after allocating/attaching the console, there is need to get stdout handle, open file using this handle and assign it to os.Stdout variable. If you need stdin you have to repeat the same for stdin.




回答3:


One problem with the solutions already posted here is that they redirect all output to the console, so if I run ./myprogram >file, the redirection to file gets lost. I've written a new module, github.com/apenwarr/fixconsole, that avoids this problem. You can use it like this:

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"))
}



回答4:


You can get the desired behavior without using -H=windowsgui; you'd basically create a standard app (with its own console window), and hide it until the program exits.

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)
    }
}

And then use it like this:

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



回答5:


If you build a windowless app you can get output with PowerShell command Out-String

.\\main.exe | out-string

your build command may look like:

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

or

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

No tricky syscalls nor kernel DLLs needed!



来源:https://stackoverflow.com/questions/23743217/printing-output-to-a-command-window-when-golang-application-is-compiled-with-ld

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!