1/* 2Title: Poly/ML Start-up code. 3 4Copyright (c) 2000, 2015, 2018, 2019 David C. J. Matthews 5 6This library is free software; you can redistribute it and/or 7modify it under the terms of the GNU Lesser General Public 8License version 2.1 as published by the Free Software Foundation. 9 10This library is distributed in the hope that it will be useful, 11but WITHOUT ANY WARRANTY; without even the implied warranty of 12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13Lesser General Public License for more details. 14 15You should have received a copy of the GNU Lesser General Public 16License along with this library; if not, write to the Free Software 17Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 18 19*/ 20 21// This was previously part of the Console and Winguiconsole file. 22 23#ifdef HAVE_CONFIG_H 24#include "config.h" 25#elif defined(_WIN32) 26#include "winconfig.h" 27#else 28#error "No configuration file" 29#endif 30 31#ifdef HAVE_STDIO_H 32#include <stdio.h> 33#endif 34 35#ifdef HAVE_WINDOWS_H 36#include <winsock2.h> // Include first to avoid conflicts 37#include <windows.h> 38#endif 39 40#ifdef HAVE_TCHAR_H 41#include <tchar.h> 42#endif 43 44#ifdef HAVE_IO_H 45#include <io.h> 46#endif 47 48#ifdef HAVE_FCNTL_H 49#include <fcntl.h> 50#endif 51 52#ifdef HAVE_ERRNO_H 53#include <errno.h> 54#endif 55 56#ifdef HAVE_ASSERT_H 57#include <assert.h> 58#define ASSERT(x) assert(x) 59#else 60#define ASSERT(x) 61#endif 62 63#include "PolyControl.h" 64#include "mpoly.h" 65#include "sighandler.h" // For RequestConsoleInterrupt 66#include "winguiconsole.h" 67#include "../polyexports.h" 68#include "processes.h" 69#include "io_internal.h" 70#include "winstartup.h" 71 72 73#ifdef _MSC_VER 74// Don't tell me about ISO C++ changes. 75#pragma warning(disable:4996) 76#endif 77 78HINSTANCE hApplicationInstance; // Application instance (exported) 79 80static HWND hDDEWindow; // Window to handle DDE requests from ML thread. 81 82static LPTSTR* lpArgs = 0; // Argument list. 83static int nArgs = 0; 84static int initDDEControl(const TCHAR *lpszName); 85static void uninitDDEControl(void); 86static DWORD dwDDEInstance; 87 88// useConsole is read in diagnostics to indicate whether to 89// put up a message box. 90bool useConsole = false; 91 92// The streams set by PolyWinMain. These are used by winbasicio. 93WinStream *standardInput, *standardOutput, *standardError; 94 95// Default DDE service name. 96#define POLYMLSERVICE _T("PolyML") 97 98#ifdef UNICODE 99#define DDECODEPAGE CP_WINUNICODE 100#else 101#define DDECODEPAGE CP_WINANSI 102#endif 103 104/* Messages interpreted by the main window thread. */ 105//#define WM_ADDTEXT WM_APP 106#define WM_DDESTART (WM_APP+1) 107#define WM_DDESTOP (WM_APP+2) 108#define WM_DDEEXEC (WM_APP+3) 109#define WM_DDESERVINIT (WM_APP+4) 110 111/* DDE requests. DDE uses an internal window for communication and so all 112DDE operations on a particular instance handle have to be performed by 113the same thread. That thread also has to check and process the message 114queue. The previous version did this by having the ML thread make the 115DDE calls and processed the message list in an "interrupt" routine. 116That complicates the Windows interface so now the ML thread sends messages 117to the main window thread to do the work. */ 118HCONV StartDDEConversation(TCHAR *serviceName, TCHAR *topicName) 119{ 120 return (HCONV)SendMessage(hDDEWindow, WM_DDESTART, (WPARAM)serviceName, (LPARAM)topicName); 121} 122 123void CloseDDEConversation(HCONV hConv) 124{ 125 SendMessage(hDDEWindow, WM_DDESTOP, 0, (LPARAM)hConv); 126} 127 128LRESULT ExecuteDDE(char *command, HCONV hConv) 129{ 130 return SendMessage(hDDEWindow, WM_DDEEXEC, (WPARAM)hConv, (LPARAM)command); 131} 132 133// This is called by the main Poly/ML thread after the arguments have been processed. 134void SetupDDEHandler(const TCHAR *lpszServiceName) 135{ 136 SendMessage(hDDEWindow, WM_DDESERVINIT, 0, (LPARAM)lpszServiceName); 137} 138 139LRESULT CALLBACK DDEWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 140{ 141 switch (uMsg) 142 { 143 case WM_DDESERVINIT: 144 return initDDEControl((const TCHAR*)lParam); 145 146 case WM_DDESTART: 147 { 148 HCONV hcDDEConv; 149 HSZ hszServiceName, hszTopicName; 150 TCHAR *serviceName = (TCHAR*)wParam; 151 TCHAR *topicName = (TCHAR*)lParam; 152 hszServiceName = 153 DdeCreateStringHandle(dwDDEInstance, serviceName, DDECODEPAGE); 154 hszTopicName = 155 DdeCreateStringHandle(dwDDEInstance, topicName, DDECODEPAGE); 156 hcDDEConv = 157 DdeConnect(dwDDEInstance, hszServiceName, hszTopicName, NULL); 158 DdeFreeStringHandle(dwDDEInstance, hszServiceName); 159 DdeFreeStringHandle(dwDDEInstance, hszTopicName); 160 if (hcDDEConv == 0) 161 { 162 // UINT nErr = DdeGetLastError(dwDDEInstance); 163 return 0; 164 } 165 return (LRESULT)hcDDEConv; 166 } 167 168 case WM_DDESTOP: 169 DdeDisconnect((HCONV)lParam); 170 return 0; 171 172 case WM_DDEEXEC: 173 { 174 HDDEDATA res; 175 LPSTR command = (LPSTR)lParam; 176 res = DdeClientTransaction((LPBYTE)command, (DWORD)(strlen(command) + 1), 177 (HCONV)wParam, 0L, 0, XTYP_EXECUTE, TIMEOUT_ASYNC, NULL); 178 if (res != 0) 179 { 180 DdeFreeDataHandle(res); 181 // Succeeded - return true; 182 return 1; 183 } 184 else if (DdeGetLastError(dwDDEInstance) == DMLERR_BUSY) 185 // If it's busy return false. 186 return 0; 187 else return -1; // An error 188 } 189 190 default: 191 return DefWindowProc(hwnd, uMsg, wParam, lParam); 192 } 193} 194 195static DWORD WINAPI MainThrdProc(LPVOID lpParameter) 196// This thread simply continues with the rest of the ML 197// initialistion. 198{ 199 exportDescription *exports = (exportDescription *)lpParameter; 200 return polymain(nArgs, lpArgs, exports); 201} 202 203// A wrapper for WinInOutStream for use with standard input that records the 204// original file type. 205class WinStdInPipeStream : public WinInOutStream 206{ 207public: 208 WinStdInPipeStream(int kind) : fKind(kind) {} 209 virtual int fileKind() { 210 return fKind; 211 } 212protected: 213 int fKind; 214}; 215 216// Thread to copy everything from the input to the output. 217class CopyThread { 218public: 219 CopyThread() : 220 hInput(INVALID_HANDLE_VALUE), hOutput(INVALID_HANDLE_VALUE) {} 221 222 bool RunCopy(HANDLE hIn, HANDLE hOut); 223private: 224 ~CopyThread(); 225 226 void threadFunction(void); 227 HANDLE hInput, hOutput; 228 static DWORD WINAPI copyThread(LPVOID lpParameter); 229}; 230 231CopyThread::~CopyThread() 232{ 233 if (hOutput != INVALID_HANDLE_VALUE) CloseHandle(hOutput); 234 if (hInput != INVALID_HANDLE_VALUE) CloseHandle(hInput); 235} 236 237// Static thread function. Deletes the CopyThread object when the copying is complete. 238// That closes the handles. 239DWORD WINAPI CopyThread::copyThread(LPVOID lpParameter) 240{ 241 CopyThread *cp = (CopyThread *)lpParameter; 242 cp->threadFunction(); 243 delete cp; 244 return 0; 245} 246 247void CopyThread::threadFunction() 248{ 249 char buffer[4096]; 250 251 while (true) { 252 DWORD dwRead; 253 if (!ReadFile(hInput, buffer, sizeof(buffer), &dwRead, NULL)) 254 return; 255 256 if (dwRead == 0) // End-of-stream 257 { 258 DWORD dwErr = GetLastError(); 259 // If we are reading from the (Windows) console and the user presses ctrl-C we 260 // may get a ERROR_OPERATION_ABORTED error. 261 if (dwErr == ERROR_OPERATION_ABORTED) 262 { 263 SetLastError(0); // Reset this. We may have a normal EOF next. 264 continue; 265 } 266 // Normal exit. Indicate EOF 267 return; 268 } 269 270 char *b = buffer; 271 do { 272 DWORD dwWritten; 273 if (!WriteFile(hOutput, b, dwRead, &dwWritten, NULL)) 274 return; 275 b += dwWritten; 276 dwRead -= dwWritten; 277 } while (dwRead != 0); 278 } 279} 280 281// Set up the copying. It closes the handles when it has finished. 282bool CopyThread::RunCopy(HANDLE hIn, HANDLE hOut) 283{ 284 DWORD dwInId; 285 hInput = hIn; 286 hOutput = hOut; 287 HANDLE hInThread = CreateThread(NULL, 0, copyThread, this, 0, &dwInId); 288 if (hInThread == NULL) return false; 289 CloseHandle(hInThread); 290 return true; 291} 292 293// Called with various control events if the input stream is a console. 294static BOOL WINAPI CtrlHandler(DWORD dwCtrlType) 295{ 296 if (dwCtrlType == CTRL_C_EVENT) 297 { 298 RequestConsoleInterrupt(); 299 return TRUE; 300 } 301 return FALSE; 302} 303 304// Main entry point. Called from WinMain with a pointer to the ML code. 305int PolyWinMain( 306 HINSTANCE hInstance, 307 HINSTANCE hPrevInstance, 308 LPSTR lpCmdLineUnused, 309 int nCmdShow, 310 exportDescription *exports 311) 312{ 313 DWORD dwInId, dwRes; 314 315 SetErrorMode(0); // Force a proper error report 316 317 hApplicationInstance = hInstance; 318 319 // If we already have standard input and standard output we 320 // don't replace them, otherwise we create a window and pipes 321 // to connect it. We use _get_osfhandle here because that 322 // checks for handles passed in in the STARTUPINFO as well as 323 // those inherited as standard handles. 324 HANDLE hStdInHandle = (HANDLE)_get_osfhandle(fileno(stdin)); 325 HANDLE hStdOutHandle = (HANDLE)_get_osfhandle(fileno(stdout)); 326 HANDLE hStdErrHandle = (HANDLE)_get_osfhandle(fileno(stderr)); 327 328 // If we don't have standard output we're going to create a console for output. 329 // We also use this for input except if we've provided standard input but not standard ouput. 330 useConsole = hStdOutHandle == INVALID_HANDLE_VALUE; 331 332 // Do we have stdin? If we do we need to create a pipe to buffer the input. 333 if (hStdInHandle != INVALID_HANDLE_VALUE) 334 { 335 // We have to capture the original type here. hStdInHandle itself will 336 // be closed when we set the new stdin and hOldStdin could be closed by 337 // the pipe input thread almost immediately if the input is a small file. 338 int originalKind = WinStream::fileTypeOfHandle(hStdInHandle); 339 340 TCHAR pipeName[MAX_PATH]; 341 newPipeName(pipeName); 342 HANDLE hOutputPipe = 343 CreateNamedPipe(pipeName, PIPE_ACCESS_OUTBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE, 344 PIPE_READMODE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS, 1, 4096, 4096, 0, NULL); 345 if (hOutputPipe == INVALID_HANDLE_VALUE) 346 return 1; 347 348 HANDLE hNewStdin = 349 CreateFile(pipeName, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL); 350 if (hNewStdin == INVALID_HANDLE_VALUE) 351 return 1; 352 // Copy everything from the original standard input to the pipe. 353 CopyThread *copyStdin = new CopyThread; 354 if (!copyStdin->RunCopy(hStdInHandle, hOutputPipe)) 355 return 1; 356 357 SetConsoleCtrlHandler(CtrlHandler, TRUE); // May fail if there's no console. 358 // Leave the original stdIn. 359 360 WinStdInPipeStream *stndIn = new WinStdInPipeStream(originalKind); 361 stndIn->openHandle(hNewStdin, OPENREAD, true); 362 standardInput = stndIn; 363 CloseHandle(hNewStdin); // Duplicated 364 } 365 else 366 { 367 // There was no standard input. If we didn't have standard output either and are using 368 // our GUI console use that for input. Otherwise open a stream on "NUL" 369 if (useConsole) 370 standardInput = createConsoleStream(); 371 else 372 { 373 WinInOutStream *inStream = new WinInOutStream; 374 // We can't use openFile here because we don't have a taskData yet. 375 HANDLE hNewStdin = 376 CreateFile(_T("NUL"), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL); 377 inStream->openHandle(hNewStdin, OPENREAD, true); 378 standardInput = inStream; 379 } 380 } 381 382 if (useConsole) 383 { 384 HANDLE hWriteToScreen = createConsoleWindow(nCmdShow); 385 if (hWriteToScreen == INVALID_HANDLE_VALUE) 386 return 1; 387 // hWriteToScreen is used as stdout . 388 // hReadFromML is what the screen thread reads from. 389 WinInOutStream *stdOut = new WinInOutStream; 390 // Safe since we duplicate the handle. 391 stdOut->openHandle(hWriteToScreen, OPENWRITE, true); 392 standardOutput = stdOut; // Pass to winbasicio 393 // Replace the standard Windows handles. This may be used in OS.Process.system. 394 SetStdHandle(STD_OUTPUT_HANDLE, hWriteToScreen); 395 // A few RTS modules use stdio for output, primarily objsize and diagnostics. 396 // Doing this causes the stdIn package to close hWriteToScreen during shut-down. 397 polyStdout = _fdopen(_open_osfhandle((INT_PTR)hWriteToScreen, _O_TEXT), "wt"); // == stdout 398 399 if (hStdErrHandle == INVALID_HANDLE_VALUE) 400 { 401 // If we didn't have stderr write any stderr output to our console. 402 WinInOutStream *stdErr = new WinInOutStream; 403 stdErr->openHandle(hWriteToScreen, OPENWRITE, true); 404 standardError = stdErr; 405 HANDLE hStderr; 406 // We definitely need a duplicate for stdio since it closes each stream on exit 407 // We may also inherit it in OS.Process.system so make it inheritable. 408 if (!DuplicateHandle(GetCurrentProcess(), hWriteToScreen, GetCurrentProcess(), &hStderr, 0, TRUE, DUPLICATE_SAME_ACCESS)) 409 return 1; 410 SetStdHandle(STD_ERROR_HANDLE, hStderr); 411 // Used in a few cases for diagnostics. 412 polyStderr = _fdopen(_open_osfhandle((INT_PTR)hStderr, _O_TEXT), "wt"); 413 } 414 } 415 else 416 { 417 // Create a copy stream 418 TCHAR pipeName[MAX_PATH]; 419 newPipeName(pipeName); 420 HANDLE hInputPipe = 421 CreateNamedPipe(pipeName, PIPE_ACCESS_INBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE, 422 PIPE_READMODE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS, 1, 4096, 4096, 0, NULL); 423 if (hInputPipe == INVALID_HANDLE_VALUE) 424 return 1; 425 HANDLE hNewStdout = 426 CreateFile(pipeName, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL); 427 if (hNewStdout == INVALID_HANDLE_VALUE) 428 return 1; 429 // Copy everything from the original standard input to the pipe. 430 CopyThread *copyStdout = new CopyThread; 431 if (!copyStdout->RunCopy(hInputPipe, hStdOutHandle)) 432 return 1; 433 WinInOutStream *standOut = new WinInOutStream; 434 standOut->openHandle(hNewStdout, OPENWRITE, true); 435 standardOutput = standOut; 436 // Leave the existing stdout and use it for diagnostics if necessary. 437 polyStdout = stdout; 438 439 if (hStdErrHandle == INVALID_HANDLE_VALUE) 440 { 441 // We had a stdout but not stderr. We could choose to direct stderr output 442 // to the provided stdout but maybe that's not what the user wants. Instead 443 // we open one on NUL. 444 polyStderr = fopen("NUL", "wt"); 445 HANDLE hStdErr = (HANDLE)_get_osfhandle(fileno(polyStderr)); 446 SetStdHandle(STD_ERROR_HANDLE, hStdErr); 447 hStdErrHandle = hStdErr; 448 } 449 // Set up a stream for writes from ML. 450 WinInOutStream *stdErr = new WinInOutStream; 451 stdErr->openHandle(hStdErrHandle, OPENWRITE, true); 452 standardError = stdErr; 453 } 454 455 // Set nArgs and lpArgs to the command line arguments. 456 // Convert the command line into Unix-style arguments. There isn't a 457 // CommandLineToArgvA function so we have to use the Unicode version and 458 // convert the results. 459 { 460 // Get the unicode args 461 LPWSTR *uniArgs = CommandLineToArgvW(GetCommandLineW(), &nArgs); 462#ifdef UNICODE 463 lpArgs = uniArgs; 464#else 465 if (uniArgs != NULL) 466 { 467 lpArgs = (LPSTR*)calloc(nArgs, sizeof(LPSTR)); 468 if (lpArgs != 0) 469 { 470 for (int i = 0; i < nArgs; i++) 471 { 472 // See how much space will be needed 473 int space = 474 WideCharToMultiByte(CP_ACP, 0, uniArgs[i], -1, NULL, 0, NULL, NULL); 475 if (space == 0) break; // Failed for some reason 476 // Allocate the space then do the conversion 477 LPSTR buff = (LPSTR)malloc(space); 478 if (buff == 0) break; 479 int result = 480 WideCharToMultiByte(CP_ACP, 0, uniArgs[i], -1, buff, space, NULL, NULL); 481 if (result == 0) { free(buff); break; } 482 lpArgs[i] = buff; 483 } 484 } 485 LocalFree(uniArgs); 486 } 487#endif 488 } 489 490 // Create an internal hidden window to handle DDE requests from the ML thread. 491 { 492 WNDCLASSEX wndClass; 493 ATOM atClass; 494 memset(&wndClass, 0, sizeof(wndClass)); 495 wndClass.cbSize = sizeof(wndClass); 496 wndClass.lpfnWndProc = DDEWndProc; 497 wndClass.hInstance = hInstance; 498 wndClass.lpszClassName = _T("PolyMLDDEWindowClass"); 499 500 if ((atClass = RegisterClassEx(&wndClass)) == 0) return 1; 501 502 hDDEWindow = CreateWindow( 503 (LPTSTR)(intptr_t)atClass, 504 _T("Poly/ML-DDE"), 505 WS_OVERLAPPEDWINDOW, 506 CW_USEDEFAULT, 507 CW_USEDEFAULT, 508 CW_USEDEFAULT, 509 CW_USEDEFAULT, 510 NULL, 511 NULL, // handle to menu or child-window identifier 512 hInstance, 513 NULL // pointer to window-creation data 514 ); 515 } 516 517 // Call the main program to do the rest of the initialisation. 518 HANDLE hMainThread = CreateThread(NULL, 0, MainThrdProc, exports, 0, &dwInId); 519 520 // Enter the main message loop. 521 while (MsgWaitForMultipleObjects(1, &hMainThread, 522 FALSE, INFINITE, QS_ALLINPUT) == WAIT_OBJECT_0 + 1) 523 { 524 MSG Msg; 525 while (PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE)) 526 { 527 TranslateMessage(&Msg); 528 DispatchMessage(&Msg); 529 } 530 } 531 532 if (!GetExitCodeThread(hMainThread, &dwRes)) dwRes = 0; 533 534 uninitDDEControl(); 535 DestroyWindow(hDDEWindow); 536 537 return dwRes; 538} 539 540HDDEDATA CALLBACK DdeCallback(UINT uType, UINT uFmt, HCONV hconv, 541 HSZ hsz1, HSZ hsz2, HDDEDATA hdata, 542 ULONG_PTR dwData1, ULONG_PTR dwData2) 543{ 544 switch (uType) 545 { 546 case XTYP_CONNECT: 547 // Client connecting. For the moment we ignore 548 // the topic. 549 return (HDDEDATA)TRUE; 550 551 case XTYP_EXECUTE: 552 { 553 // See what the message is. The only ones we 554 // handle are interrupt and terminate. 555 TCHAR buff[256]; 556 buff[0] = 0; 557 DdeGetData(hdata, (LPBYTE)buff, sizeof(buff), 0); 558 if (lstrcmpi(buff, _T(POLYINTERRUPT)) == 0) 559 { 560 RequestConsoleInterrupt(); 561 return (HDDEDATA)DDE_FACK; 562 } 563 if (lstrcmpi(buff, _T(POLYTERMINATE)) == 0) 564 { 565 processes->RequestProcessExit(0); 566 return (HDDEDATA)DDE_FACK; 567 } 568 return (HDDEDATA)DDE_FNOTPROCESSED; 569 } 570 571 default: 572 return (HDDEDATA)NULL; 573 } 574} 575 576static int initDDEControl(const TCHAR *lpszName) 577{ 578 // Start the DDE service. This receives remote requests. 579 if (DdeInitialize(&dwDDEInstance, DdeCallback, 580 APPCLASS_STANDARD | CBF_FAIL_ADVISES | CBF_FAIL_POKES | 581 CBF_FAIL_REQUESTS | CBF_SKIP_ALLNOTIFICATIONS, 0) 582 != DMLERR_NO_ERROR) 583 return 0; 584 585 // If we were given a service name we register that, 586 // otherwise we use the default name. 587 if (lpszName == 0) lpszName = POLYMLSERVICE; 588 HSZ hszServiceName = DdeCreateStringHandle(dwDDEInstance, lpszName, DDECODEPAGE); 589 if (hszServiceName == 0) return 0; 590 591 DdeNameService(dwDDEInstance, hszServiceName, 0L, DNS_REGISTER); 592 593 DdeFreeStringHandle(dwDDEInstance, hszServiceName); 594 return 1; 595} 596 597static void uninitDDEControl(void) 598{ 599 // Unregister our name(s). 600 DdeNameService(dwDDEInstance, 0L, 0L, DNS_UNREGISTER); 601 // DdeAbandonTransaction(dwDDEInstance, 0L, 0L); 602 // Unitialise DDE. 603 DdeUninitialize(dwDDEInstance); 604} 605