[Win32] Streams redirecten tussen twee console apps

Pagina: 1
Acties:

  • FTPlus
  • Registratie: Februari 2003
  • Laatst online: 10-11-2024
Ik ben bezig met een commandline applicatie die een process kan opstarten en deze in de gaten houden.Het grootste gedeelte is af en getest, maar nu wilde ik er een extra feature in bouwen. Ik wil het mogelijk maken dat de input/output stream van de process doorgestuurd kan worden naar de applicatie zelf, of naar een bestand. Het door sturen naar een bestand is me al gelukt, maar hoe ik het van de process naar de applicatie stuur is me een raadsel.

Ik gebruik de volgende opzet: (C++, niet relevante code daargelaten)
code:
1
2
3
4
5
6
7
8
9
10
11
STARTUPINFO si = {sizeof (si)}; // Verwijzing naar startupinfo struct

si.dwFlags = STARTF_USESTDHANDLES; // Gebruik maken van aangepaste stream handles

si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);   // Ontvang handle van
si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); // oorsprokelijke applicatie.
si.hStdError = GetStdHandle(STD_ERROR_HANDLE);

CreateProcess(0, command, 0, 0, true, DETACHED_PROCESS, env, workpath, &si, &pi);

WaitForSingleObject(pi.hProcess, timeout); // Wachten totdat process gesloten is


Ik gebruik de DETACHED_PROCESS flag om ervoor te zorgen dat de gestarte process actief blijft indien de applicatie afgesloten word. Als ik deze flag weglaat komt alle input/output braaf in de console van m'n applicatie terecht, maar met blijft mijn console leeg.

Ik heb een donkerbruin vermoeden dan het ligt aan GetStdHandle. Misschien geeft deze wel niet een goede handle terug, omdat de process nu een aparte thread heeft gekregen.

Na bezoek aan google en msdn stuitte ik op de functies CreatePipe en DuplicateHandle, maar ik heb na lezen van wat site's nog geen flauw idee hoe ik die hiervoor moet gebruiken.

Weten jullie misschien hoe ik bij het creeëren van een nieuw process de input en output kan doorsturen naar de parent applicatie?

-=Waiz=-


Verwijderd

Weet je zeker dat DETACHED_PROCESS doet wat je denkt dat het doet? Aan de documentatie te zijn start dat alleen een nieuwe console; is wat je wilt om je nieuwe proces onafhankelijk te maken van het oude niet CREATE_NEW_PROCESS_GROUP? En het verschil tussen DETACHED_PROCESS en CREATE_NEW_CONSOLE is me eerlijk gezegd ook niet duidelijk. Relevante stukken uit de documentatie:
CREATE_NEW_CONSOLE
The new process has a new console, instead of inheriting the parent's console. This flag cannot be used with the DETACHED_PROCESS flag.

CREATE_NEW_PROCESS_GROUP
The new process is the root process of a new process group. The process group includes all processes that are descendants of this root process. The process identifier of the new process group is the same as the process identifier, which is returned in the lpProcessInformation parameter. Process groups are used by the GenerateConsoleCtrlEvent function to enable sending a CTRL+C or CTRL+BREAK signal to a group of console processes.

DETACHED_PROCESS
For console processes, the new process does not have access to the console of the parent process. The new process can call the AllocConsole function at a later time to create a new console. This flag cannot be used with the CREATE_NEW_CONSOLE flag.
Hoe het ook zij -- waar je naar op zoek bent is het starten van een child process met redirected input en output. Dat is nogal een ingewikkeld gedoe op Windows, maar het komt in het kort op het volgende keer:

• Creëer 2 pipes, een voor invoer en een voor uitvoer. Maak deze pipes (of eigenlijk de handles ernaar) inheritable. Maak 3 pipes als je stderr apart wilt redirecten.
• Voor de stdin pipe maak je een kopie naar de schrijfkant van de pipe, verwijdert bij het kopiëren de inheritable vlag van de handle, en je sluit de inheritable handle. Doe hetzelfde voor de leeskant van de stdin pipe.
• Dit is een uitgebreide manier om te zorgen dat elke pipe een kant met een inheritable handle, en een kant met een niet-inheritable handle heeft. Dat is nodig want als je child process teveel handles inherit, kun je de pijp niet meer sluiten.
• Nu kun je het child process starten met CreateProcess. Laat handles inheriten, en zet de goeie kanten van de respectievelijke pijpen in de velden van si.hStdInput etc.
• Bij het createn van een kindproces worden ook weer kopieën van handles geinherit, dus nu bestaan er twee handles naar bijvoorbeeld de schrijfkant van je stdout pijp -- eentje voor het parent proces en eentje voor het kindproces. Een pijp gaat pas dicht als alle handles ernaar gesloten zijn; als de twee handles zouden blijven bestaan, zou de pijp dus niet dichtgaan als het child process afsluit, omdat het parent process ook nog een handle naar de pijp heeft. Oplossing: sluit ook nog de parent kopieën van de inheritable handles.

En hier is nog een voorbeeld uit de Win32 programmer's reference, wat ongeveer hetzelfde doet, maar SetStdHandle() gebruikt om de handles door te geven aan het kind proces, in plaats van dat met de velden van STARTUPINFO te doen.

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
#include <stdio.h>  
#include <windows.h> 
 
#define BUFSIZE 4096 
 
HANDLE hChildStdinRd, hChildStdinWr, hChildStdinWrDup, 
   hChildStdoutRd, hChildStdoutWr, hChildStdoutRdDup, 
   hInputFile, hSaveStdin, hSaveStdout; 
 
BOOL CreateChildProcess(VOID); 
VOID WriteToPipe(VOID); 
VOID ReadFromPipe(VOID); 
VOID ErrorExit(LPTSTR); 
VOID ErrMsg(LPTSTR, BOOL); 
 
DWORD main(int argc, char *argv[]) 
{ 
   SECURITY_ATTRIBUTES saAttr; 
   BOOL fSuccess; 
 
// Set the bInheritHandle flag so pipe handles are inherited. 

 
   saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 
   saAttr.bInheritHandle = TRUE; 
   saAttr.lpSecurityDescriptor = NULL; 
 
   // The steps for redirecting child process's STDOUT: 
   //     1. Save current STDOUT, to be restored later. 
   //     2. Create anonymous pipe to be STDOUT for child process. 
   //     3. Set STDOUT of the parent process to be write handle of 
   //        the pipe, so it is inherited by the child process. 
   //     4. Create a noninheritable duplicate of the read handle and

   //        close the inheritable read handle. 
 
// Save the handle to the current STDOUT. 
 
   hSaveStdout = GetStdHandle(STD_OUTPUT_HANDLE); 
 
// Create a pipe for the child process's STDOUT. 
 
   if (! CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0)) 
      ErrorExit("Stdout pipe creation failed\n"); 
 
// Set a write handle to the pipe to be STDOUT. 
 
   if (! SetStdHandle(STD_OUTPUT_HANDLE, hChildStdoutWr)) 
      ErrorExit("Redirecting STDOUT failed"); 

 
// Create noninheritable read handle and close the inheritable read 
// handle. 

    fSuccess = DuplicateHandle(GetCurrentProcess(), hChildStdoutRd,
        GetCurrentProcess(), &hChildStdoutRdDup , 0,
        FALSE,
        DUPLICATE_SAME_ACCESS);
    if( !fSuccess )
        ErrorExit("DuplicateHandle failed");
    CloseHandle(hChildStdoutRd);

   // The steps for redirecting child process's STDIN: 
   //     1.  Save current STDIN, to be restored later. 

   //     2.  Create anonymous pipe to be STDIN for child process. 
   //     3.  Set STDIN of the parent to be the read handle of the 
   //         pipe, so it is inherited by the child process. 
   //     4.  Create a noninheritable duplicate of the write handle, 
   //         and close the inheritable write handle. 
 
// Save the handle to the current STDIN. 
 
   hSaveStdin = GetStdHandle(STD_INPUT_HANDLE); 
 
// Create a pipe for the child process's STDIN. 

 
   if (! CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0)) 
      ErrorExit("Stdin pipe creation failed\n"); 
 
// Set a read handle to the pipe to be STDIN. 
 
   if (! SetStdHandle(STD_INPUT_HANDLE, hChildStdinRd)) 
      ErrorExit("Redirecting Stdin failed"); 
 
// Duplicate the write handle to the pipe so it is not inherited. 
 
   fSuccess = DuplicateHandle(GetCurrentProcess(), hChildStdinWr, 
      GetCurrentProcess(), &hChildStdinWrDup, 0, 
      FALSE,                  // not inherited 

      DUPLICATE_SAME_ACCESS); 
   if (! fSuccess) 
      ErrorExit("DuplicateHandle failed"); 
 
   CloseHandle(hChildStdinWr); 
 
// Now create the child process. 
 
   if (! CreateChildProcess()) 
      ErrorExit("Create process failed"); 
 
// After process creation, restore the saved STDIN and STDOUT. 
 
   if (! SetStdHandle(STD_INPUT_HANDLE, hSaveStdin)) 
      ErrorExit("Re-redirecting Stdin failed\n"); 
 
   if (! SetStdHandle(STD_OUTPUT_HANDLE, hSaveStdout)) 

      ErrorExit("Re-redirecting Stdout failed\n"); 
 
// Get a handle to the parent's input file. 
 
   if (argc > 1) 
      hInputFile = CreateFile(argv[1], GENERIC_READ, 0, NULL, 
         OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL); 
   else 
      hInputFile = hSaveStdin; 
 
   if (hInputFile == INVALID_HANDLE_VALUE) 
      ErrorExit("no input file\n"); 
 
// Write to pipe that is the standard input for a child process. 
 
   WriteToPipe(); 
 
// Read from pipe that is the standard output for child process. 

 
   ReadFromPipe(); 
 
   return 0; 
} 
 
BOOL CreateChildProcess() 
{ 
   PROCESS_INFORMATION piProcInfo; 
   STARTUPINFO siStartInfo; 
 
// Set up members of STARTUPINFO structure. 
 
   ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
   siStartInfo.cb = sizeof(STARTUPINFO); 
 
// Create the child process. 
 
   return CreateProcess(NULL, 
      "child",       // command line 
      NULL,          // process security attributes 
      NULL,          // primary thread security attributes 

      TRUE,          // handles are inherited 
      0,             // creation flags 
      NULL,          // use parent's environment 
      NULL,          // use parent's current directory 
      &siStartInfo,  // STARTUPINFO pointer 
      &piProcInfo);  // receives PROCESS_INFORMATION 
}
 
VOID WriteToPipe(VOID) 
{ 
   DWORD dwRead, dwWritten; 
   CHAR chBuf[BUFSIZE]; 
 
// Read from a file and write its contents to a pipe. 
 
   for (;;) 
   { 
      if (! ReadFile(hInputFile, chBuf, BUFSIZE, &dwRead, NULL) || 

         dwRead == 0) break; 
      if (! WriteFile(hChildStdinWrDup, chBuf, dwRead, 
         &dwWritten, NULL)) break; 
   } 
 
// Close the pipe handle so the child process stops reading. 
 
   if (! CloseHandle(hChildStdinWrDup)) 
      ErrorExit("Close pipe failed\n"); 
} 
 
VOID ReadFromPipe(VOID) 
{ 
   DWORD dwRead, dwWritten; 
   CHAR chBuf[BUFSIZE]; 
   HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE); 

// Close the write end of the pipe before reading from the 

// read end of the pipe. 
 
   if (!CloseHandle(hChildStdoutWr)) 
      ErrorExit("Closing handle failed"); 
 
// Read output from the child process, and write to parent's STDOUT. 
 
   for (;;) 
   { 
      if( !ReadFile( hChildStdoutRdDup, chBuf, BUFSIZE, &dwRead, 
         NULL) || dwRead == 0) break; 
      if (! WriteFile(hSaveStdout, chBuf, dwRead, &dwWritten, NULL)) 
         break; 
   } 
} 
 
VOID ErrorExit (LPTSTR lpszMessage) 
{ 
   fprintf(stderr, "%s\n", lpszMessage); 

   ExitProcess(0); 
} 

  • FTPlus
  • Registratie: Februari 2003
  • Laatst online: 10-11-2024
Heel erg bedankt voor je uitleg, ik heb het uitgetest en nu werkt het. :D
Weet je zeker dat DETACHED_PROCESS doet wat je denkt dat het doet? Aan de documentatie te zijn start dat alleen een nieuwe console; is wat je wilt om je nieuwe proces onafhankelijk te maken van het oude niet CREATE_NEW_PROCESS_GROUP? En het verschil tussen DETACHED_PROCESS en CREATE_NEW_CONSOLE is me eerlijk gezegd ook niet duidelijk.
Het enige verschil is dat CREATE_NEW_CONSOLE ook daadwerkelijk een nieuw console venster opent, terwijl je dat bij DETACHED_PROCESS handmatig moet doen. Als je dat niet doet lult hij zegmaar in de ruimte, dat kan soms wel eens wenselijk zijn. :)

Voorheen maakte ik gebruik van WaitForSingleObject, om (met een timeout) te wachten totdat het process is afgesloten. Is er een manier om deze functie te blijven gebruiken terwijl tijdens het wachten alle input en output doorgestuurd word? Ik had de write en read functies al even in een loopje gezet, maar dan hij wacht telkens op input en heeft de timeout geen effect. ;(

-=Waiz=-


  • FTPlus
  • Registratie: Februari 2003
  • Laatst online: 10-11-2024
Het is opgelost! :D Ik maak nu gebruik van extra threads die de input en output behandelen. Nog bedankt voor de hulp. Ik zal voor de compleetheid van dit topic mijn (sterk ingekorte) source hier plaatsen.

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#include <windows.h>
#include <stdio.h>

//------------------------------------------------------------------------------

PROCESS_INFORMATION pi;
STARTUPINFO si = {sizeof (si)};
SECURITY_ATTRIBUTES sa = {sizeof (sa), 0, true};

HANDLE hParent, hChild, hPipeInput, hPipeOutput, hPipeError;
HANDLE hInStd, hInOld, hInOldDup, hInNew;
HANDLE hOutStd, hOutOld, hOutOldDup, hOutNew;
HANDLE hErrStd, hErrOld, hErrOldDup, hErrNew;

DWORD WINAPI PipeInput(LPVOID arg);
DWORD WINAPI PipeOutput(LPVOID arg);
DWORD WINAPI PipeError(LPVOID arg);

//------------------------------------------------------------------------------

int main()
{
    hInStd = GetStdHandle(STD_INPUT_HANDLE);
    hOutStd = GetStdHandle(STD_OUTPUT_HANDLE);
    hErrStd = GetStdHandle(STD_ERROR_HANDLE);
    hParent = GetCurrentProcess();
    
    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
    
    CreatePipe(&hInNew, &hInOld, &sa, 0);
    CreatePipe(&hOutOld, &hOutNew, &sa, 0);
    CreatePipe(&hErrOld, &hErrNew, &sa, 0);
    
    DuplicateHandle(hParent, hInOld, hParent, &hInOldDup , 0, false, DUPLICATE_SAME_ACCESS);
    DuplicateHandle(hParent, hOutOld, hParent, &hOutOldDup , 0, false, DUPLICATE_SAME_ACCESS);
    DuplicateHandle(hParent, hErrOld, hParent, &hErrOldDup , 0, false, DUPLICATE_SAME_ACCESS);
    
    CloseHandle(hInOld);
    CloseHandle(hOutOld);
    CloseHandle(hErrOld);
    
    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
    
    si.dwFlags = STARTF_USESTDHANDLES;
    si.hStdInput = hInNew;
    si.hStdOutput = hOutNew;
    si.hStdError = hErrNew;
    
    CreateProcess(0, "pulsar", 0, 0, true, DETACHED_PROCESS, 0, 0, &si, &pi);
    
    hChild = pi.hProcess;
    
    // Threads die input en output afhandelen
    hPipeInput = CreateThread(&sa, 0, PipeInput, 0, 0, 0);
    hPipeOutput = CreateThread(&sa, 0, PipeOutput, 0, 0, 0);
    hPipeError = CreateThread(&sa, 0, PipeError, 0, 0, 0);
    
    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
    
    WaitForSingleObject(hChild, 4000);
    
    TerminateProcess(hChild, 0);
    
    TerminateThread(hPipeInput, 0);
    TerminateThread(hPipeOutput, 0);
    TerminateThread(hPipeError, 0);
    
    CloseHandle(pi.hThread);
    CloseHandle(pi.hProcess);
    
    CloseHandle(hPipeInput);
    CloseHandle(hPipeOutput);
    CloseHandle(hPipeError);
    
    return (0);
}

//------------------------------------------------------------------------------

DWORD WINAPI PipeInput(LPVOID arg)
{
    DWORD read, written;
    CHAR buffer[4096];
    
    while(1)
        if (ReadFile(hInStd, buffer, 4096, &read, NULL) && read)
            WriteFile(hInOldDup, buffer, read, &written, NULL);
}

DWORD WINAPI PipeOutput(LPVOID arg)
{
    DWORD read, written;
    CHAR buffer[4096];
    
    while(1)
        if (ReadFile(hOutOldDup, buffer, 4096, &read, NULL) && read)
            WriteFile(hOutStd, buffer, read, &written, NULL);
}

DWORD WINAPI PipeError(LPVOID arg)
{
    DWORD read, written;
    CHAR buffer[4096];
    
    while(1)
        if (ReadFile(hErrOldDup, buffer, 4096, &read, NULL) && read)
            WriteFile(hErrStd, buffer, read, &written, NULL);
}

[ Voor 0% gewijzigd door FTPlus op 18-03-2007 01:15 . Reden: toch maar ff kleurtjes ;) ]

-=Waiz=-