Merge pull request #826 from tim-caper/winconsole.c

use MS Windows API for Unicode and/or stdout support
master
Marius Kintel 2014-06-09 22:00:32 -04:00
commit aac577635a
1 changed files with 172 additions and 76 deletions

View File

@ -8,9 +8,15 @@
The .com version is a 'wrapper' for the .exe version. If you call
'openscad' with no extension from a script or shell, the .com version
is prioritized by the OS and feeds the GUI stdout to the console. We use
pure C to minimize binary size when cross-compiling (~10kbytes). See Also:
is prioritized by the OS and feeds the GUI stdout to the console.
We keep it in 'pure c' to minimize binary size on the cross-compile.
Note the .com file is not a 'real' .com file. It is just an .exe
that has been renamed to a .com during the Windows(TM) OpenSCAD package build.
See Also:
../doc/windows_issues.txt
http://stackoverflow.com/questions/493536/can-one-executable-be-both-a-console-and-gui-app
http://blogs.msdn.com/b/oldnewthing/archive/2009/01/01/9259142.aspx
http://blogs.msdn.com/b/junfeng/archive/2004/02/06/68531.aspx
@ -19,93 +25,183 @@
Open Group popen() documentation
inkscapec by Jos Hirth work at http://kaioa.com
Nop Head's OpenSCAD_cl at github.com
ImageMagick's utilities, like convert.cc
http://www.i18nguy.com/unicode/c-unicode.html
TODO:
Work with unicode: http://www.i18nguy.com/unicode/c-unicode.html
A few other notes:
We throw out argc/argv and pull the w_char commandline using special
Win(TM) functions. We then strip out the 'openscad' program name, but
leave the rest of the commandline in tact in a tail. Then we call
'openscad.exe'.
stderr is used to support writing of STL/PNG etc. output to stdout.
MS Windows API used instead of popen() to append stdout to stderr and
avoid running cmd.exe (so save some resources).
TODO:
Fix printing of unicode on console.
*/
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <process.h>
#include <io.h>
#include <fcntl.h>
/*#include <stdio.h>*/
#include <string.h>
// want to use system definitions instead like #include <system.h>
#ifndef WIFEXITED
#define WIFEXITED(S) (((S) & 0xff) == 0)
#endif
#ifndef WEXITSTATUS
#define WEXITSTATUS(S) (((S) >> 8) & 0xff)
#endif
#define MAXCMDLEN /*64000*/ 32768 /* MS Windows limit */
#define BUFFSIZE 42
int main( int argc, char * argv[] )
{
FILE *cmd_stdout;
char cmd[MAXCMDLEN];
char buffer[BUFFSIZE];
int pclose_result;
int i;
int result = 0;
unsigned n; // total number of characters in cmd
static const char exe_str[] = "openscad.exe";
static const char redirect_str[] = " 2>&1"; // capture stderr and stdout
memcpy(cmd, exe_str, (n = sizeof(exe_str)-1)); // without \0
for ( i = 1 ; i < argc ; ++i ) {
register char *s;
/*bool*/ int quote;
cmd[n++] = ' ';
// MS Windows special characters need quotation
// See issues #440, #441 & #479
quote = NULL != strpbrk((s = argv[i]), " \"&'<>^|\t");
if (quote) cmd[n++] = '"';
while (*s) { // copy & check
// The following test is compomise between brevity, clarity and performance.
// It could be boiled down to: if ('"' == s[strspn(s,"\\")])
// or expanded to avoid repetitive passes of strspn() over same data.
if ('"' == *s || ('\\' == *s && '"' == s[strspn(s,"\\")])) cmd[n++] = '\\';
cmd[n++] = *s++;
if (n >= MAXCMDLEN-sizeof(redirect_str)) {
fprintf(stderr, "Command line length exceeds limit of %d\n", MAXCMDLEN);
return 1;
}
}
if (quote) cmd[n++] = '"';
// manage MS Windows error codes
// Do not use fprintf() etc. due to call from thread.
static void displayError(char *msg, DWORD errcode) {
HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
char buffer[1024];
if (msg && *msg) {
WriteFile(hError, msg, strlen(msg), NULL, NULL);
}
memcpy(&cmd[n], redirect_str, sizeof(redirect_str)); // including \0
if (ERROR_SUCCESS == errcode) return;
if (FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, errcode, 0,
buffer, sizeof(buffer), NULL)) {
WriteFile(hError, buffer, strlen(buffer), NULL, NULL);
}
}
cmd_stdout = _popen( cmd, "rt" );
if ( cmd_stdout == NULL ) {
fprintf(stderr, "Error opening _popen for command: %s", cmd );
perror( "Error message" );
static void displayLastError(char *msg) {
displayError(msg, GetLastError());
}
typedef struct {
/*volatile*/ int status;
HANDLE hRead, hOutput;
STARTUPINFOW startupInfo;
PROCESS_INFORMATION processInfo;
} WATCH_INFO;
static DWORD WINAPI watchdog(LPVOID arg) {
#define info ((WATCH_INFO*)arg)
DWORD bc;
char buffer[1024];
for (info->status=0;;) {
if (!ReadFile(info->hRead,buffer,sizeof(buffer),&bc,NULL)) {
displayLastError("Failed to read pipe\n");
/* return 1; */ info->status = 1;
} else if (bc) {
(void)WriteFile(info->hOutput, buffer, bc, NULL, NULL);
} else break; // closed pipe
}
return 0;
#undef info
}
#define EXE_NAME "openscad.exe"
#define ___WIDECHARTEXT(s) L##s
#define W(x) ___WIDECHARTEXT(x)
#define IS_WHITESPACE(c) (' ' == (c) || '\t' == (c))
#define MAXCMDLEN 32768 /* MS Windows limit */
int main(int argc, char *argv[]) {
HANDLE hWrite, curr_proc;
SECURITY_ATTRIBUTES sa;
WATCH_INFO info;
wchar_t cmd[MAXCMDLEN];
DWORD status;
int result = 0;
register int i;
/*bool*/ int quote, preserve_stdout;
register wchar_t *cmdline = GetCommandLineW();
// Look for the end of executable
// There is no need to check for escaped double quotes here
// because MS Windows file name can not contain such quotes
for (quote=0; *cmdline && (quote || !IS_WHITESPACE(*cmdline)); ++cmdline) {
if ('"' == *cmdline) quote ^= 1;
}
if (IS_WHITESPACE(*cmdline)) {
while (IS_WHITESPACE(*(cmdline+1))) ++cmdline;
}
(void)wcscpy(cmd, W(EXE_NAME));
if (wcslen(cmd) + wcslen(cmdline) >= MAXCMDLEN) {
// fprintf(stderr, "Command line length exceeds limit of %d\n", MAXCMDLEN);
// avoid fprintf() to decrease executable size
displayError("Command line length exceeds limit\n", ERROR_SUCCESS);
return 1;
}
(void)wcscat(cmd, cmdline);
for(;;) {
if (NULL == fgets(buffer, BUFFSIZE, cmd_stdout)) {
if ( ferror( cmd_stdout ) ) {
fprintf(stderr, "Error reading from stdout of %s\n", cmd);
result = 1;
// look for '-o -' combination
for (preserve_stdout=FALSE, i=/*sic!*/2; i<argc; ++i) {
register char *s = argv[i];
if ('-' == s[0] && '\0' == s[1]) // it is "-"
if (!strcmp("-o", argv[i-1])) {
preserve_stdout = TRUE; break;
}
if ( feof( cmd_stdout ) ) {
break;
}
} else {
fputs(buffer, stdout);
}
ZeroMemory(&info, sizeof(info));
info.startupInfo.cb = sizeof(info.startupInfo);
info.startupInfo.dwFlags = STARTF_USESTDHANDLES;
info.startupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
info.startupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
info.hOutput = GetStdHandle(STD_ERROR_HANDLE);
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = FALSE;
if (!CreatePipe(&info.hRead, &hWrite, &sa, 1024)) {
displayLastError("CreatePipe(): ");
return 1;
}
// make inheritable write handle(s)
curr_proc = GetCurrentProcess();
if (!DuplicateHandle(curr_proc, hWrite, curr_proc,
&info.startupInfo.hStdError, 0L, TRUE, DUPLICATE_SAME_ACCESS)) {
displayLastError("DuplicateHandle(): ");
return 1;
}
if (!preserve_stdout) {
info.hOutput = info.startupInfo.hStdOutput;
#if 1 // Should I really duplicate handle or plain copy suffice?
if (!DuplicateHandle(curr_proc, hWrite, curr_proc,
&info.startupInfo.hStdOutput, 0L, TRUE, DUPLICATE_SAME_ACCESS)) {
displayLastError("DuplicateHandle(): ");
return 1;
}
#else
info.startupInfo.hStdOutput = info.startupInfo.hStdError;
#endif
}
(void)CloseHandle(hWrite);
if (!CreateProcessW(NULL, cmd, NULL, NULL, TRUE,
CREATE_UNICODE_ENVIRONMENT, NULL, NULL,
&info.startupInfo, &info.processInfo)) {
// fwprintf(stderr, L"Cannot run: %s\n", cmd);
// avoid fwprintf() to decrease executable size
// fputws(L"Cannot run: ", stderr); fputws(cmd, stderr); fputws(L"\n", stderr);
displayLastError("Cannot run " EXE_NAME "\n");
return 1;
}
// Create thread to work around ReadFile() blocking
if (!CreateThread(NULL, 32768, watchdog, &info, 0, NULL)) {
displayLastError("CreateThread(): ");
result = 1;
}
// WaitForSingleObject() returns zero on success
if (WaitForSingleObject(info.processInfo.hProcess, INFINITE)) {
displayLastError("WaitForSingleObject(): ");
result = 1;
}
if (!GetExitCodeProcess(info.processInfo.hProcess, &status)) {
displayLastError("GetExitCodeProcess(): ");
result = 1;
}
if (!result) {
if (info.status) result = info.status;
else result = status; // return value of openscad.exe
}
pclose_result = _pclose( cmd_stdout );
// perror() applicable with return value of -1 only!
// Avoid stupid "Error: No Error" message
if (pclose_result == -1) {
perror("Error while closing stdout for command");
result = 1;
} else if (!result) {
result = WIFEXITED(pclose_result) ? WEXITSTATUS(pclose_result) : 1;
}
// All currently open streams and handles will be
// closed automatically upon the process termination.
return result;
}