Saturday, September 3, 2016

Windows Console on Demand in Go

There are two kinds of Windows programs: Windows GUI or Console Application. There could be third case:  process, which does not have any visual representation at all and needs to be running in the background. For that case program maybe be built as Windows GUI, but it should not have any code, which creates Windows objects.  But what if  you want from application sometimes  to be silent and sometimes  to be verbose and to have a console. Possible use cases are:
  • Printing version of the utility if started with special flag (like -V)
  • Program needs complicated tune up through the  command line arguments or configuration file, but normally is  running on the background. It would be beneficially to have a choice either run it with console or run it quietly.
That could be achieved by compiling application as Windows GUI and allocating  or attaching console to the process during run-time if needed. There are a lot of examples how to do that in C/C++. When I myself looked for the solution several years ago I have found this link  helpful. Fragment of the code, which creates console may look like this.

 // taken from: http://www.codeproject.com/KB/dialog/ConsoleAdapter.aspx

 AllocConsole();
 int fdout = _open_osfhandle((long)GetStdHandle(STD_OUTPUT_HANDLE), _O_TEXT);
 FILE* fout = _fdopen(fdout, "w");
 *stdout = *fout;

 int fdin = _open_osfhandle((long)GetStdHandle(STD_INPUT_HANDLE), _O_TEXT);
 FILE* fin = _fdopen(fdin, "r");
 *stdin = *fin;


Nowadays I found myself  programming more and more in Go. While initially Go compiler was intended for Linux only,  that is not the case anymore. Windows now is the first class citizen in the Go world. Recently I needed to create application, which may open the console if that demanded by  command line argument, but work invisibly  otherwise . To my surprise there were not that many online information how to do that in Go. I did find one answered question in StackOverflow. Alas, proposed solution did not work for me out of the box. But using it as starting point and after some googling I have found workable solution. Here is sample of Go application, which will allocate console, print some prompt, wait for keyboard input and quit.

// 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)
}
I would like to point some details about syscall.Syscall invocation for AllocConsole :
  • This function  has five arguments. It is different of what you will find in the online Go Doc . Looks like doc references Linux definition, which is not the same as Windows.
  • Second parameter here is number of arguments in dll function. For our case it is 0.
  • Function returns three variables, but only first is really meaningful. It is the result returned by AllocConsole function. Second variables always 0. Third variables  is an  error, but in cannot be analyzed in the regular Go way as far as it is never nil.  Still it could be useful: in case of success its value is different from value on failure (for example if application was build with no windowsgui flag and already has the console ) .


No comments:

Post a Comment