xref: /AOO41X/main/desktop/win32/source/guistdio/guistdio.inc (revision e3a7b91b49e4d9ec2856213648b0df1853a72db8)
1/**************************************************************
2 *
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements.  See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership.  The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License.  You may obtain a copy of the License at
10 *
11 *   http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied.  See the License for the
17 * specific language governing permissions and limitations
18 * under the License.
19 *
20 *************************************************************/
21
22
23
24#define UNICODE
25#define WIN32_LEAN_AND_MEAN
26#ifdef _MSC_VER
27#pragma warning(push,1) // disable warnings within system headers
28#endif
29#include <windows.h>
30#ifdef _MSC_VER
31#pragma warning(pop)
32#endif
33
34#define _UNICODE
35#include <tchar.h>
36
37#include <string.h>
38#include <stdlib.h>
39#include <systools/win32/uwinapi.h>
40
41#include <stdio.h>
42
43#ifdef UNOPKG
44
45DWORD passOutputToConsole(HANDLE readPipe, HANDLE console)
46{
47    BYTE aBuffer[1024];
48    DWORD dwRead = 0;
49    HANDLE hReadPipe = readPipe;
50    BOOL fSuccess;
51    DWORD dwWritten;
52
53    //Indicates that we read an odd number of bytes. That is, we only read half of the last
54    //wchar_t
55    bool bIncompleteWchar = false;
56    //fprintf, fwprintf will both send char data without the terminating zero.
57    //fwprintf converts the unicode string first.
58    //We expect here to receive unicode without the terminating zero.
59    //unopkg and the extension manager code MUST
60    //use dp_misc::writeConsole instead of using fprintf, etc.
61
62    DWORD dwToRead = sizeof(aBuffer);
63    BYTE * pBuffer = aBuffer;
64    while ( ReadFile( hReadPipe, pBuffer, dwToRead, &dwRead, NULL ) )
65    {
66        //If the previous ReadFile call read an odd number of bytes, then the last one was
67        //put at the front of the buffer. We increase the number of read bytes by one to reflect
68        //that one byte.
69        if (bIncompleteWchar)
70            dwRead++;
71        //We must make sure that only complete wchar_t|s are written. WriteConsole takes
72        //the number of wchar_t|s as argument. ReadFile, however, reads bytes.
73        bIncompleteWchar = dwRead % 2 ? true : false;
74        if (bIncompleteWchar)
75        {
76            //To test this case, give aBuffer a small odd size, e.g. aBuffer[3]
77            //The last byte, which is the incomplete wchar_t (half of it), will not be written.
78            fSuccess = WriteConsoleW( console, aBuffer,
79                (dwRead - 1) / 2, &dwWritten, NULL );
80
81            //Move the last byte to the front of the buffer, so that it is the start of the
82            //next string
83            aBuffer[0] = aBuffer[dwRead - 1];
84
85            //Make sure that ReadFile does not overwrite the first byte the next time
86            dwToRead = sizeof(aBuffer) - 1;
87            pBuffer = aBuffer + 1;
88
89        }
90        else
91        {   //We have read an even number of bytes. Therefore, we do not put the last incomplete
92            //wchar_t at the front of the buffer. We will use the complete buffer the next time
93            //when ReadFile is called.
94            dwToRead = sizeof(aBuffer);
95            pBuffer = aBuffer;
96            fSuccess = WriteConsoleW( console,
97                aBuffer, dwRead / 2, &dwWritten, NULL );
98        }
99    }
100
101    return 0;
102}
103
104#endif
105
106#ifdef UNOPKG
107DWORD WINAPI OutputThread( LPVOID pParam )
108{
109    return passOutputToConsole((HANDLE)pParam, GetStdHandle( STD_OUTPUT_HANDLE ));
110}
111
112#else
113DWORD WINAPI OutputThread( LPVOID pParam )
114{
115    BYTE    aBuffer[256];
116    DWORD   dwRead = 0;
117    HANDLE  hReadPipe = (HANDLE)pParam;
118    while ( ReadFile( hReadPipe, &aBuffer, sizeof(aBuffer), &dwRead, NULL ) )
119    {
120        BOOL    fSuccess;
121        DWORD   dwWritten;
122
123        fSuccess = WriteFile( GetStdHandle( STD_OUTPUT_HANDLE ), aBuffer, dwRead, &dwWritten, NULL );
124    }
125
126    return 0;
127}
128#endif
129//---------------------------------------------------------------------------
130// Thread that reads from child process standard error pipe
131//---------------------------------------------------------------------------
132
133#ifdef UNOPKG
134DWORD WINAPI ErrorThread( LPVOID pParam )
135{
136    return passOutputToConsole((HANDLE)pParam, GetStdHandle( STD_ERROR_HANDLE ));
137}
138
139#else
140DWORD WINAPI ErrorThread( LPVOID pParam )
141{
142    BYTE    aBuffer[256];
143    DWORD   dwRead = 0;
144    HANDLE  hReadPipe = (HANDLE)pParam;
145
146    while ( ReadFile( hReadPipe, &aBuffer, sizeof(aBuffer), &dwRead, NULL ) )
147    {
148        BOOL    fSuccess;
149        DWORD   dwWritten;
150
151        fSuccess = WriteFile( GetStdHandle( STD_ERROR_HANDLE ), aBuffer, dwRead, &dwWritten, NULL );
152    }
153
154    return 0;
155}
156#endif
157//---------------------------------------------------------------------------
158// Thread that writes to child process standard input pipe
159//---------------------------------------------------------------------------
160#ifdef UNOPKG
161
162DWORD WINAPI InputThread( LPVOID pParam )
163{
164    DWORD   dwRead = 0;
165    HANDLE  hWritePipe = (HANDLE)pParam;
166
167    //We need to read in the complete input until we encounter a new line before
168    //converting to Unicode. This is necessary because the input string can use
169    //characters of one, two, and more bytes. If the last character is not
170    //complete, then it will not be converted properly.
171
172    //Find out how a new line (0xd 0xa) looks like with the used code page.
173    //Characters may have one or multiple bytes and different byte ordering
174    //can be used (little and big endian);
175    int cNewLine = WideCharToMultiByte(
176        GetConsoleCP(), 0, L"\r\n", 2, NULL, 0, NULL, NULL);
177    char * mbBuff = new char[cNewLine];
178    WideCharToMultiByte(
179        GetConsoleCP(), 0, L"\r\n", 2, mbBuff, cNewLine, NULL, NULL);
180
181    const size_t dwBufferSize = 256;
182    char* readBuf = (char*) malloc(dwBufferSize);
183    int readAll = 0;
184    size_t curBufSize = dwBufferSize;
185
186    while ( ReadFile( GetStdHandle( STD_INPUT_HANDLE ),
187                      readBuf + readAll,
188                      curBufSize - readAll, &dwRead, NULL ) )
189    {
190        readAll += dwRead;
191        int lastBufSize = curBufSize;
192        //Grow the buffer if necessary
193        if (readAll > curBufSize * 0.7)
194        {
195            curBufSize *= 2;
196            readBuf = (char *) realloc(readBuf, curBufSize);
197        }
198
199        //If the buffer was filled completely then
200        //there could be more input coming. But if we read from the console
201        //and the console input fits exactly in the buffer, then the next
202        //ReadFile would block until the user presses return, etc.
203        //Therefor we check if last character is a new line.
204        //To test this, set dwBufferSize to 4 and enter "no". This should produce
205        //4 bytes with most code pages.
206        if ( readAll == lastBufSize
207             && memcmp(readBuf + lastBufSize - cNewLine, mbBuff, cNewLine) != 0)
208        {
209            //The buffer was completely filled and the last byte(s) are no
210            //new line, so there is more to come.
211            continue;
212        }
213        //Obtain the size of the buffer for the converted string.
214        int sizeWBuf = MultiByteToWideChar(
215            GetConsoleCP(), MB_PRECOMPOSED, readBuf, readAll, NULL, 0);
216
217        wchar_t * wideBuf = new wchar_t[sizeWBuf];
218
219        //Do the conversion.
220        MultiByteToWideChar(
221            GetConsoleCP(), MB_PRECOMPOSED, readBuf, readAll, wideBuf, sizeWBuf);
222
223        BOOL    fSuccess;
224        DWORD   dwWritten;
225        fSuccess = WriteFile( hWritePipe, wideBuf, sizeWBuf * 2, &dwWritten, NULL );
226        delete[] wideBuf;
227        readAll = 0;
228    }
229    delete[] mbBuff;
230    free(readBuf);
231    return 0;
232}
233#else
234DWORD WINAPI InputThread( LPVOID pParam )
235{
236    BYTE    aBuffer[256];
237    DWORD   dwRead = 0;
238    HANDLE  hWritePipe = (HANDLE)pParam;
239
240    while ( ReadFile( GetStdHandle( STD_INPUT_HANDLE ), &aBuffer, sizeof(aBuffer), &dwRead, NULL ) )
241    {
242        BOOL    fSuccess;
243        DWORD   dwWritten;
244
245        fSuccess = WriteFile( hWritePipe, aBuffer, dwRead, &dwWritten, NULL );
246    }
247
248    return 0;
249}
250#endif
251
252//---------------------------------------------------------------------------
253// Thread that waits until child process reached input idle
254//---------------------------------------------------------------------------
255
256DWORD WINAPI WaitForUIThread( LPVOID pParam )
257{
258    HANDLE  hProcess = (HANDLE)pParam;
259
260#ifndef UNOPKG
261    if ( !_tgetenv( TEXT("UNOPKG") ) )
262        WaitForInputIdle( hProcess, INFINITE );
263#endif
264
265    return 0;
266}
267
268
269//---------------------------------------------------------------------------
270// Ctrl-Break handler that terminates the child process if Ctrl-C was pressed
271//---------------------------------------------------------------------------
272
273HANDLE  hTargetProcess = INVALID_HANDLE_VALUE;
274
275BOOL WINAPI CtrlBreakHandler(
276  DWORD   // control signal type
277)
278{
279    TerminateProcess( hTargetProcess, 255 );
280    return TRUE;
281}
282
283
284//---------------------------------------------------------------------------
285
286//---------------------------------------------------------------------------
287
288#ifdef __MINGW32__
289int main( int, char ** )
290#else
291int _tmain( int, _TCHAR ** )
292#endif
293{
294    TCHAR               szTargetFileName[MAX_PATH] = TEXT("");
295    STARTUPINFO         aStartupInfo;
296    PROCESS_INFORMATION aProcessInfo;
297
298    ZeroMemory( &aStartupInfo, sizeof(aStartupInfo) );
299    aStartupInfo.cb = sizeof(aStartupInfo);
300    aStartupInfo.dwFlags = STARTF_USESTDHANDLES;
301
302    // Create an output pipe where the write end is inheritable
303
304    HANDLE  hOutputRead, hOutputWrite;
305
306    if ( CreatePipe( &hOutputRead, &hOutputWrite, NULL, 0 ) )
307    {
308        HANDLE  hTemp;
309
310        DuplicateHandle( GetCurrentProcess(), hOutputWrite, GetCurrentProcess(), &hTemp, 0, TRUE, DUPLICATE_SAME_ACCESS );
311        CloseHandle( hOutputWrite );
312        hOutputWrite = hTemp;
313
314        aStartupInfo.hStdOutput = hOutputWrite;
315    }
316
317    // Create an error pipe where the write end is inheritable
318
319    HANDLE  hErrorRead, hErrorWrite;
320
321    if ( CreatePipe( &hErrorRead, &hErrorWrite, NULL, 0 ) )
322    {
323        HANDLE  hTemp;
324
325        DuplicateHandle( GetCurrentProcess(), hErrorWrite, GetCurrentProcess(), &hTemp, 0, TRUE, DUPLICATE_SAME_ACCESS );
326        CloseHandle( hErrorWrite );
327        hErrorWrite = hTemp;
328
329        aStartupInfo.hStdError = hErrorWrite;
330    }
331
332    // Create an input pipe where the read end is inheritable
333
334    HANDLE  hInputRead, hInputWrite;
335
336    if ( CreatePipe( &hInputRead, &hInputWrite, NULL, 0 ) )
337    {
338        HANDLE  hTemp;
339
340        DuplicateHandle( GetCurrentProcess(), hInputRead, GetCurrentProcess(), &hTemp, 0, TRUE, DUPLICATE_SAME_ACCESS );
341        CloseHandle( hInputRead );
342        hInputRead = hTemp;
343
344        aStartupInfo.hStdInput = hInputRead;
345    }
346
347    // Get image path with same name but with .exe extension
348
349    TCHAR               szModuleFileName[MAX_PATH];
350
351    GetModuleFileName( NULL, szModuleFileName, MAX_PATH );
352    _TCHAR  *lpLastDot = _tcsrchr( szModuleFileName, '.' );
353    if ( lpLastDot && 0 == _tcsicmp( lpLastDot, _T(".COM") ) )
354    {
355        size_t len = lpLastDot - szModuleFileName;
356        _tcsncpy( szTargetFileName, szModuleFileName, len );
357        _tcsncpy( szTargetFileName + len, _T(".EXE"), sizeof(szTargetFileName)/sizeof(szTargetFileName[0]) - len );
358    }
359
360    // Create process with same command line, environment and stdio handles which
361    // are directed to the created pipes
362
363    BOOL    fSuccess = CreateProcess(
364        szTargetFileName,
365        GetCommandLine(),
366        NULL,
367        NULL,
368        TRUE,
369        0,
370        NULL,
371        NULL,
372        &aStartupInfo,
373        &aProcessInfo );
374
375    if ( fSuccess )
376    {
377        // These pipe ends are inherited by the child process and no longer used
378        CloseHandle( hOutputWrite );
379        CloseHandle( hErrorWrite );
380        CloseHandle( hInputRead );
381
382        // Set the Ctrl-Break handler
383        hTargetProcess = aProcessInfo.hProcess;
384        SetConsoleCtrlHandler( CtrlBreakHandler, TRUE );
385
386        // Create threads that redirect remote pipe io to current process's console stdio
387
388        DWORD   dwOutputThreadId, dwErrorThreadId, dwInputThreadId;
389
390        HANDLE  hOutputThread = CreateThread( NULL, 0, OutputThread, (LPVOID)hOutputRead, 0, &dwOutputThreadId );
391        HANDLE  hErrorThread = CreateThread( NULL, 0, OutputThread, (LPVOID)hErrorRead, 0, &dwErrorThreadId );
392        HANDLE  hInputThread = CreateThread( NULL, 0, InputThread, (LPVOID)hInputWrite, 0, &dwInputThreadId );
393
394        // Create thread that wait until child process entered input idle
395
396        DWORD   dwWaitForUIThreadId;
397        HANDLE  hWaitForUIThread = CreateThread( NULL, 0, WaitForUIThread, (LPVOID)aProcessInfo.hProcess, 0, &dwWaitForUIThreadId );
398
399        DWORD   dwWaitResult;
400        HANDLE  hObjects[] =
401            {
402                hTargetProcess,
403                hWaitForUIThread,
404                hOutputThread,
405                hErrorThread
406            };
407
408 #ifdef UNOPKG
409        dwWaitResult = WaitForMultipleObjects( elementsof(hObjects), hObjects, TRUE, INFINITE );
410 #else
411        bool    bDetach = false;
412        int     nOpenPipes = 2;
413        do
414        {
415            dwWaitResult = WaitForMultipleObjects( elementsof(hObjects), hObjects, FALSE, INFINITE );
416
417            switch ( dwWaitResult )
418            {
419            case WAIT_OBJECT_0: // The child process has terminated
420            case WAIT_OBJECT_0 + 1: // The child process entered input idle
421                bDetach = true;
422                break;
423            case WAIT_OBJECT_0 + 2: // The remote end of stdout pipe was closed
424            case WAIT_OBJECT_0 + 3: // The remote end of stderr pipe was closed
425                bDetach = --nOpenPipes <= 0;
426                break;
427            default: // Something went wrong
428                bDetach = true;
429                break;
430            }
431        } while( !bDetach );
432
433#endif
434
435        CloseHandle( hOutputThread );
436        CloseHandle( hErrorThread );
437        CloseHandle( hInputThread );
438        CloseHandle( hWaitForUIThread );
439
440        DWORD   dwExitCode = 0;
441        GetExitCodeProcess( aProcessInfo.hProcess, &dwExitCode );
442        CloseHandle( aProcessInfo.hProcess );
443        CloseHandle( aProcessInfo.hThread );
444
445        return dwExitCode;
446    }
447
448    return -1;
449}
450