source: trunk/source/cocoa-ide/WaltConsole/WaltConsole.c @ 12800

Last change on this file since 12800 was 12800, checked in by gb, 11 years ago

An initial altconsole-like application for Windows (this just uses a
Win32 console window.) I think that we ultimately want to use something
like this in Cocotron (rather than continuing to do what we've been doing)
for a variety of reasons: we'd like to be able to plug in replacement
"console" applications (SLIME, a remote CCL, ...), want that application
to be able to run and display output even after the .app has crashed or
exited, don't want to redirect stdout/stderr (since NSLogged messages tend
to confuse users), don't want to create buffered stdio streams in the
lisp kernel and then think that we need to flush them at arbitrary times,
etc.

This seems to behave as well as it can under Vista; before hooking it up,
it'd be good to know if it also works under Win7. It uses an undocumented
Windows kernel function to find the pid of the parent process (usually
wx86cl.exe) and uses a function from the "psapi" library that may have
moved elsewhere/been renamed under Win7; I can't quite parse what the
MSDN documentation says about this.

Doing:

? (run-program "/path/to/WaltConsole.exe"

()
:input :stream
:output :stream
:error :output
:wait nil)

should create an EXTERNAL-PROCESS without erring; writing text to the
EXTERNAL-PROCESS-INPUT-STREAM of that EXTERNAL-PROCESS (and forcing
output) should cause the process to display a console window with that
text; anything typed in the console window (followed by the enter key)
should be readable by reading from the EXTERNAL-PROCESS-OUTPUT-STREAM
of the external process. Quitting from the lisp (or possibly just
closing one or both of those streams) should cause the console window
to note that the parent process has exited and provide an opportunity
to save the console window's output (via some awkward cut/paste operation)
before the subprocess exits.

If that all works in Win7, then I think that we should figure out how to
(optionally) install this in a standalone Cocotron bundle, activate it
on startup of a standalone .app (should be similar to how that's done on
OSX), and remove some of the code that we've been using. Win32 consoles
are ugly and stupid and we can always replace this implementation with
something a bit more feature-rich; that's part of the idea.

File size: 4.4 KB
Line 
1#include <windows.h>
2#include <psapi.h>
3#include <stdio.h>
4#include <string.h>
5
6
7struct ProcessBasicInformation {
8  DWORD ExitStatus;
9  PVOID PebBaseAddress;
10  DWORD AffinityMask;
11  DWORD BasePriority;
12  ULONG UniqueProcessId;
13  ULONG InheritedFromUniqueProcessId;
14};
15
16/* Try to get the (Windows) pid of the parent process; return it, or
17   return 0 on failure.  There doesn't seem to be a higher-level way
18   to do this.
19*/
20
21typedef LONG (__stdcall *FPTR_NtQueryInformationProcess ) (HANDLE, INT, PVOID, ULONG, PULONG);
22
23ULONG
24getppid()
25{
26  ULONG ppid = 0, pid = GetCurrentProcessId();
27  HANDLE hcurrent = OpenProcess(PROCESS_QUERY_INFORMATION,FALSE,pid);
28  if (hcurrent) {
29    FPTR_NtQueryInformationProcess NtQueryInformationProcess = (FPTR_NtQueryInformationProcess) GetProcAddress(GetModuleHandleA("ntdll"), "NtQueryInformationProcess");
30    if (NtQueryInformationProcess != NULL) {
31      struct ProcessBasicInformation pbi;
32      if (NtQueryInformationProcess(hcurrent, 0, (void *)&pbi, sizeof(pbi), NULL) == 0) {
33        ppid = pbi.InheritedFromUniqueProcessId;
34      }
35    }
36    CloseHandle(hcurrent);
37  }
38  return ppid;
39}
40
41char *
42parent_process_name(ULONG ppid)
43{
44  char procname[1024], *result = NULL;
45  DWORD namelen;
46  HANDLE hprocess = OpenProcess(PROCESS_QUERY_INFORMATION|PROCESS_VM_READ, FALSE, ppid);
47
48  if (hprocess) {
49    namelen = GetModuleBaseNameA(hprocess, NULL, procname, sizeof(procname)-1);
50    if (namelen && (namelen <= sizeof(procname))) {
51      result = strdup(procname);
52    }
53    CloseHandle(hprocess);
54  }
55  return result;
56}
57
58
59int
60write_pending_console_input(HANDLE input, HANDLE output) 
61{
62  DWORD ninput_events, i, n, nread, nwritten;
63  char buf[1024];
64  INPUT_RECORD ir;
65
66  switch (WaitForSingleObject(input, 100)) {
67  case WAIT_TIMEOUT:
68    return 0;
69    break;
70  case WAIT_OBJECT_0:
71    ninput_events = 0;
72    if (GetNumberOfConsoleInputEvents(input, &ninput_events)) {
73      for (i = 0; i < ninput_events; i++) {
74        PeekConsoleInput(input, &ir, 1, &n);
75        if ((ir.EventType == KEY_EVENT)) {
76          if (!ReadFile(input, buf, sizeof(buf), &nread, NULL)) {
77            return -1;
78          }
79          if (!WriteFile(output, buf, nread, &nwritten, NULL)) {
80            return -1;
81          }
82          return 0;
83        } else {
84          ReadConsoleInput(input, &ir, 1, &n);
85        }
86      }
87    }
88    return 0;
89    break;
90
91  default:
92    return -1;
93    break;
94  }
95}
96 
97
98int
99write_pipe_input_to_console(HANDLE hpipe, HANDLE conout) 
100{
101  unsigned char buf[1024];
102  DWORD navail, n, m;
103
104  while (PeekNamedPipe(hpipe, NULL, 0, NULL, &navail, NULL) != 0) {
105    if (navail == 0) {
106      return 0;
107    }
108    if (navail > sizeof(buf)) {
109      n = sizeof(buf);
110    } else {
111      n = navail;
112    }
113    if (!ReadFile(hpipe, buf, n, &m, NULL)) {
114      return -1;
115    }
116    if (!WriteFile(conout, buf, m, &n, NULL)) {
117      return -1;
118    }
119  }
120  return -1;
121}
122
123int APIENTRY WinMain(HINSTANCE a,HINSTANCE b,LPSTR c,int d)
124{
125  HANDLE in, out, conin, conout;
126  DWORD navail, err;
127  ULONG ppid;
128  HWND window;
129  HMENU menu;
130  char title[2048], *procname, buf[1024];
131
132  in = GetStdHandle(STD_INPUT_HANDLE);
133  out = GetStdHandle(STD_OUTPUT_HANDLE);
134
135  while (1) {
136    navail = 0;
137    if (PeekNamedPipe(in, NULL, 0, NULL, &navail, NULL) == 0) {
138      exit(0);                  /* error or EOF */
139    }
140    if (navail != 0) {
141      break;
142    }
143    Sleep(100);
144  }
145
146  AllocConsole();
147
148  conin = CreateFileA("CONIN$",GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
149  conout = CreateFileA("CONOUT$",GENERIC_WRITE,FILE_SHARE_WRITE,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
150
151  ppid = getppid();
152  procname = parent_process_name(ppid);
153  if (!procname) {
154    procname = "<Unknown>";
155  }
156  sprintf(title, "WaltConsole for %s (pid 0x%x)", procname, ppid);
157  SetConsoleTitleA(
158title);
159
160  window = GetConsoleWindow();
161  menu = GetSystemMenu(window, FALSE);
162  EnableMenuItem(menu, SC_CLOSE, MF_BYCOMMAND|MF_GRAYED);
163
164  ShowWindow(window, SW_SHOW);
165  ShowWindow(window, SW_SHOW);
166
167  do {
168    if (write_pipe_input_to_console(in, conout) < 0) {
169      break;
170    }
171    write_pending_console_input(conin, out);
172  } while (1);
173
174  EnableMenuItem(menu, SC_CLOSE, MF_BYCOMMAND|MF_ENABLED);
175  sprintf(buf,"\n\nProcess %s (pid 0x%x) has exited; press any key to close this window.", procname, ppid);
176  WriteFile(conout, buf, strlen(buf), &navail, NULL);
177  FlushConsoleInputBuffer(conin);
178  ReadConsoleA(conin,buf,1,&navail,NULL);
179
180}
Note: See TracBrowser for help on using the repository browser.