1
1
#include < matplot/util/popen.h>
2
2
3
3
#ifdef _WIN32
4
+ #include < vector>
5
+ #include < memory>
6
+ #include < mutex>
4
7
#include < io.h>
5
8
6
9
namespace matplot ::detail {
10
+ // / Stores file and process handles between hiddenPopen and hiddenPclose
11
+ struct WinProcess
12
+ {
13
+ FILE *file{nullptr };
14
+ PROCESS_INFORMATION pi{}; // contains process handle to be closed in pclose
15
+ static void store (std::unique_ptr<WinProcess> proc) {
16
+ std::scoped_lock lock{mtx};
17
+ procs.push_back (std::move (proc));
18
+ }
19
+ static std::unique_ptr<WinProcess> retrieve (FILE* file) {
20
+ auto res = std::unique_ptr<WinProcess>{};
21
+ auto lock = std::scoped_lock{mtx};
22
+ for (auto it = procs.begin (); it != procs.end (); ++it) {
23
+ if ((*it)->file == file) {
24
+ res = std::move (*it);
25
+ procs.erase (it);
26
+ break ;
27
+ }
28
+ }
29
+ return res;
30
+ }
31
+ private:
32
+ static std::vector<std::unique_ptr<WinProcess>> procs;
33
+ static std::mutex mtx;
34
+ };
35
+ std::vector<std::unique_ptr<WinProcess>> WinProcess::procs{};
36
+ std::mutex WinProcess::mtx{};
37
+
7
38
// Function to create a new process and return a FILE pointer for
8
39
// input/output. Mimics _popen behaviour, but does not open console windows
9
40
// in GUI apps
10
41
FILE *hiddenPopen (const char *command, const char *mode) {
11
- SECURITY_ATTRIBUTES saAttr;
12
42
HANDLE hChildStdinRd, hChildStdinWr, hStdin, hStdout;
13
- STARTUPINFO si;
14
- PROCESS_INFORMATION pi;
15
-
43
+ SECURITY_ATTRIBUTES saAttr{};
16
44
// Set up security attributes for inheritable handles
17
45
saAttr.nLength = sizeof (SECURITY_ATTRIBUTES);
18
46
saAttr.bInheritHandle = TRUE ;
@@ -27,45 +55,51 @@ namespace matplot::detail {
27
55
// Ensure the write handle to the pipe is not inherited by child
28
56
// processes
29
57
if (!SetHandleInformation (hChildStdinWr, HANDLE_FLAG_INHERIT, 0 )) {
30
- errno = GetLastError (); // emulate POSIX behavior
58
+ auto err = GetLastError (); // emulate POSIX behavior
31
59
CloseHandle (hChildStdinRd);
60
+ errno = err;
32
61
return nullptr ;
33
62
}
34
63
35
64
// Create a pipe for the child process's output
36
65
if (!CreatePipe (&hStdin, &hStdout, &saAttr, 0 )) {
37
- errno = GetLastError (); // emulate POSIX behavior
66
+ auto err = GetLastError (); // emulate POSIX behavior
38
67
CloseHandle (hChildStdinRd);
39
68
CloseHandle (hChildStdinWr);
69
+ errno = err;
40
70
return nullptr ;
41
71
}
42
72
43
73
// Ensure the read handle to the output pipe is not inherited by child
44
74
// processes
45
75
if (!SetHandleInformation (hStdout, HANDLE_FLAG_INHERIT, 0 )) {
46
- errno = GetLastError (); // emulate POSIX behavior
76
+ auto err = GetLastError (); // emulate POSIX behavior
47
77
CloseHandle (hChildStdinRd);
48
78
CloseHandle (hChildStdinWr);
49
79
CloseHandle (hStdin);
80
+ errno = err;
50
81
return nullptr ;
51
82
}
52
83
53
84
// Configure STARTUPINFO structure for the new process
85
+ STARTUPINFO si{};
54
86
ZeroMemory (&si, sizeof (STARTUPINFO));
55
87
si.cb = sizeof (STARTUPINFO);
56
88
si.hStdError = hStdout;
57
89
si.hStdOutput = hStdout;
58
90
si.hStdInput = hChildStdinRd;
59
91
si.dwFlags |= STARTF_USESTDHANDLES;
92
+ auto p = std::make_unique<WinProcess>();
60
93
61
94
// Create the child process, while hiding the window
62
95
if (!CreateProcess (NULL , const_cast <char *>(command), NULL , NULL , TRUE ,
63
- CREATE_NO_WINDOW, NULL , NULL , &si, &pi)) {
64
- errno = GetLastError (); // emulate POSIX behavior
96
+ CREATE_NO_WINDOW, NULL , NULL , &si, &p-> pi )) {
97
+ auto err = GetLastError (); // emulate POSIX behavior
65
98
CloseHandle (hChildStdinRd);
66
99
CloseHandle (hChildStdinWr);
67
100
CloseHandle (hStdin);
68
101
CloseHandle (hStdout);
102
+ errno = err;
69
103
return nullptr ;
70
104
}
71
105
@@ -78,33 +112,55 @@ namespace matplot::detail {
78
112
FILE *file = _fdopen (_open_osfhandle ((intptr_t )hChildStdinWr, 0 ), mode);
79
113
80
114
if (file == nullptr ) {
81
- errno = GetLastError (); // emulate POSIX behavior
115
+ auto err = GetLastError ();
82
116
CloseHandle (hChildStdinWr);
83
117
CloseHandle (hStdin);
84
- CloseHandle (pi.hProcess );
85
- CloseHandle (pi.hThread );
118
+ CloseHandle (p->pi .hProcess );
119
+ CloseHandle (p->pi .hThread );
120
+ errno = err; // emulate POSIX behavior
86
121
return nullptr ;
87
122
}
88
-
123
+ p->file = file;
124
+ WinProcess::store (std::move (p));
89
125
return file;
90
126
}
91
127
92
128
// Function to close the process and retrieve its exit code
93
129
int hiddenPclose (FILE *file) {
94
- HANDLE hFile = (HANDLE)_get_osfhandle (_fileno (file));
130
+ // The following does not work for GetExitCodeProcess:
131
+ // HANDLE hFile = (HANDLE)_get_osfhandle(_fileno(file));
132
+ if (file == nullptr ) {
133
+ errno = ECHILD;
134
+ return -1 ;
135
+ }
136
+ // Close the input to child:
95
137
fclose (file);
138
+ // Retrieve the child process info:
139
+ auto p = WinProcess::retrieve (file);
140
+ if (p == nullptr ) {
141
+ errno = ECHILD;
142
+ return -1 ;
143
+ }
144
+ HANDLE hThread = p->pi .hThread ;
145
+ HANDLE hProc = p->pi .hProcess ;
96
146
// Wait for the process to finish
97
- if (auto r = WaitForSingleObject (hFile, INFINITE); r != WAIT_OBJECT_0) {
147
+ if (auto r = WaitForSingleObject (hProc, INFINITE); r != WAIT_OBJECT_0) {
148
+ CloseHandle (hThread);
149
+ CloseHandle (hProc);
98
150
errno = ECHILD; // emulate POSIX behavior
99
151
return -1 ;
100
152
}
101
153
// Retrieve the exit code
102
154
DWORD exitCode;
103
- if (auto r = GetExitCodeProcess (hFile, &exitCode); r == 0 ) {
104
- errno = ECHILD; // emulate POSIX behavior
155
+ if (BOOL r = GetExitCodeProcess (hProc, &exitCode); !r) {
156
+ auto err = GetLastError ();
157
+ CloseHandle (hThread);
158
+ CloseHandle (hProc);
159
+ errno = err; // emulate POSIX behavior
105
160
return -1 ;
106
161
}
107
- CloseHandle (hFile);
162
+ CloseHandle (hThread);
163
+ CloseHandle (hProc);
108
164
return exitCode;
109
165
}
110
166
} // namespace matplot::detail
0 commit comments