Skip to content

Commit f874e9e

Browse files
committed
A work around for popen/pclose: complicated but works
1 parent dded044 commit f874e9e

File tree

1 file changed

+74
-18
lines changed

1 file changed

+74
-18
lines changed

source/matplot/util/popen.cpp

Lines changed: 74 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,46 @@
11
#include <matplot/util/popen.h>
22

33
#ifdef _WIN32
4+
#include <vector>
5+
#include <memory>
6+
#include <mutex>
47
#include <io.h>
58

69
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+
738
// Function to create a new process and return a FILE pointer for
839
// input/output. Mimics _popen behaviour, but does not open console windows
940
// in GUI apps
1041
FILE *hiddenPopen(const char *command, const char *mode) {
11-
SECURITY_ATTRIBUTES saAttr;
1242
HANDLE hChildStdinRd, hChildStdinWr, hStdin, hStdout;
13-
STARTUPINFO si;
14-
PROCESS_INFORMATION pi;
15-
43+
SECURITY_ATTRIBUTES saAttr{};
1644
// Set up security attributes for inheritable handles
1745
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
1846
saAttr.bInheritHandle = TRUE;
@@ -27,45 +55,51 @@ namespace matplot::detail {
2755
// Ensure the write handle to the pipe is not inherited by child
2856
// processes
2957
if (!SetHandleInformation(hChildStdinWr, HANDLE_FLAG_INHERIT, 0)) {
30-
errno = GetLastError(); // emulate POSIX behavior
58+
auto err = GetLastError(); // emulate POSIX behavior
3159
CloseHandle(hChildStdinRd);
60+
errno = err;
3261
return nullptr;
3362
}
3463

3564
// Create a pipe for the child process's output
3665
if (!CreatePipe(&hStdin, &hStdout, &saAttr, 0)) {
37-
errno = GetLastError(); // emulate POSIX behavior
66+
auto err = GetLastError(); // emulate POSIX behavior
3867
CloseHandle(hChildStdinRd);
3968
CloseHandle(hChildStdinWr);
69+
errno = err;
4070
return nullptr;
4171
}
4272

4373
// Ensure the read handle to the output pipe is not inherited by child
4474
// processes
4575
if (!SetHandleInformation(hStdout, HANDLE_FLAG_INHERIT, 0)) {
46-
errno = GetLastError(); // emulate POSIX behavior
76+
auto err = GetLastError(); // emulate POSIX behavior
4777
CloseHandle(hChildStdinRd);
4878
CloseHandle(hChildStdinWr);
4979
CloseHandle(hStdin);
80+
errno = err;
5081
return nullptr;
5182
}
5283

5384
// Configure STARTUPINFO structure for the new process
85+
STARTUPINFO si{};
5486
ZeroMemory(&si, sizeof(STARTUPINFO));
5587
si.cb = sizeof(STARTUPINFO);
5688
si.hStdError = hStdout;
5789
si.hStdOutput = hStdout;
5890
si.hStdInput = hChildStdinRd;
5991
si.dwFlags |= STARTF_USESTDHANDLES;
92+
auto p = std::make_unique<WinProcess>();
6093

6194
// Create the child process, while hiding the window
6295
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
6598
CloseHandle(hChildStdinRd);
6699
CloseHandle(hChildStdinWr);
67100
CloseHandle(hStdin);
68101
CloseHandle(hStdout);
102+
errno = err;
69103
return nullptr;
70104
}
71105

@@ -78,33 +112,55 @@ namespace matplot::detail {
78112
FILE *file = _fdopen(_open_osfhandle((intptr_t)hChildStdinWr, 0), mode);
79113

80114
if (file == nullptr) {
81-
errno = GetLastError(); // emulate POSIX behavior
115+
auto err = GetLastError();
82116
CloseHandle(hChildStdinWr);
83117
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
86121
return nullptr;
87122
}
88-
123+
p->file = file;
124+
WinProcess::store(std::move(p));
89125
return file;
90126
}
91127

92128
// Function to close the process and retrieve its exit code
93129
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:
95137
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;
96146
// 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);
98150
errno = ECHILD; // emulate POSIX behavior
99151
return -1;
100152
}
101153
// Retrieve the exit code
102154
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
105160
return -1;
106161
}
107-
CloseHandle(hFile);
162+
CloseHandle(hThread);
163+
CloseHandle(hProc);
108164
return exitCode;
109165
}
110166
} // namespace matplot::detail

0 commit comments

Comments
 (0)