Skip to content

Commit 0eb1c63

Browse files
committed
add WSJT-X code: wrap QProcess to avoid inherited handles causing issues on Windows
1 parent 76d7ad3 commit 0eb1c63

5 files changed

+158
-5
lines changed

CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ set (wsjt_qt_CXXSRCS
175175
revision_utils.cpp
176176
WFPalette.cpp
177177
Radio.cpp
178+
NonInheritingProcess.cpp
178179
IARURegions.cpp
179180
Bands.cpp
180181
Modes.cpp

NonInheritingProcess.cpp

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
#include "NonInheritingProcess.hpp"
2+
3+
#ifdef Q_OS_WIN
4+
#ifndef WIN32_LEAN_AND_MEAN
5+
#define WIN32_LEAN_AND_MEAN
6+
#endif
7+
#ifndef NOMINMAX
8+
#define NOMINMAX
9+
#endif
10+
#ifndef _UNICODE
11+
#define _UNICODE
12+
#endif
13+
#ifdef _WIN32_WINNT
14+
#undef _WIN32_WINNT
15+
#endif
16+
#define _WIN32_WINNT 0x0601
17+
#include <windows.h>
18+
#endif
19+
20+
#include <memory>
21+
#include <functional>
22+
23+
#include "pimpl_impl.hpp"
24+
25+
namespace
26+
{
27+
#ifdef Q_OS_WIN
28+
struct start_info_deleter
29+
{
30+
void operator () (STARTUPINFOEXW * si)
31+
{
32+
if (si->lpAttributeList)
33+
{
34+
::DeleteProcThreadAttributeList (si->lpAttributeList);
35+
}
36+
delete si;
37+
}
38+
};
39+
#endif
40+
}
41+
42+
class NonInheritingProcess::impl
43+
{
44+
public:
45+
#ifdef Q_OS_WIN
46+
void extend_CreateProcessArguments (QProcess::CreateProcessArguments * args)
47+
{
48+
//
49+
// Here we modify the CreateProcessArguments structure to use a
50+
// STARTUPINFOEX extended argument to CreateProcess. In that we
51+
// set up a list of handles for the new process to inherit. By
52+
// doing this we stop all inherited handles from being
53+
// inherited. Unfortunately UpdateProcThreadAttribute does not let
54+
// us set up an empty handle list, so we populate the list with
55+
// the three standard stream handles that QProcess::start has set
56+
// up as Pipes to do IPC. Even though these Pipe handles are
57+
// created with inheritance disabled, UpdateProcThreadAtribute and
58+
// CreateProcess don't seem to mind, which suits us fine.
59+
//
60+
// Note: that we cannot just clear the inheritHandles flag as that
61+
// stops the standard stream handles being inherited which breaks
62+
// our IPC using std(in|out|err). Only be using a
63+
// PROC_THREAD_ATTRIBUTE_HANDLE_LIST attribute in a STARTUPINFOEX
64+
// structure can we avoid the all or nothing behaviour of
65+
// CreateProcess /w respect to handle inheritance.
66+
//
67+
BOOL fSuccess;
68+
SIZE_T size {0};
69+
LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList = nullptr;
70+
::InitializeProcThreadAttributeList (nullptr, 1, 0, &size);
71+
lpAttributeList = reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST> (::HeapAlloc (::GetProcessHeap (), 0, size));
72+
fSuccess = !!lpAttributeList;
73+
if (fSuccess)
74+
{
75+
fSuccess = ::InitializeProcThreadAttributeList (lpAttributeList, 1, 0, &size);
76+
}
77+
if (fSuccess)
78+
{
79+
// empty list of handles
80+
fSuccess = ::UpdateProcThreadAttribute (lpAttributeList, 0,
81+
PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
82+
&args->startupInfo->hStdInput, 3 * sizeof (HANDLE),
83+
nullptr, 0);
84+
}
85+
if (fSuccess)
86+
{
87+
start_info_.reset (new STARTUPINFOEXW);
88+
start_info_->StartupInfo = *args->startupInfo;
89+
start_info_->StartupInfo.cb = sizeof (STARTUPINFOEXW);
90+
start_info_->lpAttributeList = lpAttributeList;
91+
args->startupInfo = reinterpret_cast<Q_STARTUPINFO*> (start_info_.get ());
92+
args->flags |= EXTENDED_STARTUPINFO_PRESENT;
93+
}
94+
}
95+
96+
using start_info_type = std::unique_ptr<STARTUPINFOEXW, start_info_deleter>;
97+
start_info_type start_info_;
98+
#endif
99+
};
100+
101+
NonInheritingProcess::NonInheritingProcess (QObject * parent)
102+
: QProcess {parent}
103+
{
104+
#ifdef Q_OS_WIN
105+
using namespace std::placeholders;
106+
107+
// enable cleanup after process starts or fails to start
108+
connect (this, &QProcess::started, [this] {m_->start_info_.reset ();});
109+
connect (this, &QProcess::errorOccurred, [this] (QProcess::ProcessError) {m_->start_info_.reset ();});
110+
setCreateProcessArgumentsModifier (std::bind (&NonInheritingProcess::impl::extend_CreateProcessArguments, &*m_, _1));
111+
#endif
112+
}
113+
114+
NonInheritingProcess::~NonInheritingProcess ()
115+
{
116+
}

NonInheritingProcess.hpp

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#ifndef NON_INHERITING_PROCESS_HPP__
2+
#define NON_INHERITING_PROCESS_HPP__
3+
4+
#include <QProcess>
5+
#include "pimpl_h.hpp"
6+
7+
class QObject;
8+
9+
//
10+
// class NonInheritingProcess - Manage a process without it inheriting
11+
// all inheritable handles
12+
//
13+
// On MS Windows QProcess creates sub-processes which inherit all
14+
// inheritable handles, and handles on Windows are inheritable by
15+
// default. This can cause the lifetime of objects to be unexpectedly
16+
// extended, which in turn can cause unexpected errors. The motivation
17+
// for this class was implementing log file rotation using the Boost
18+
// log library. The current log file's handle gets inherited by any
19+
// long running sub-process started by QProcess and that causes a
20+
// sharing violation when attempting to rename the log file on
21+
// rotation, even though the log library closes the current log file
22+
// before trying to rename it.
23+
//
24+
class NonInheritingProcess
25+
: public QProcess
26+
{
27+
public:
28+
NonInheritingProcess (QObject * parent = nullptr);
29+
~NonInheritingProcess ();
30+
31+
private:
32+
class impl;
33+
pimpl<impl> m_;
34+
};
35+
#endif

mainwindow.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -738,7 +738,7 @@ MainWindow::MainWindow(bool multiple, QSettings * settings, QSharedMemory *shdme
738738

739739
createStatusBar();
740740

741-
connect(&proc_jtdxjt9, SIGNAL(readyReadStandardOutput()),this, SLOT(readFromStdout()));
741+
connect(&proc_jtdxjt9, &QProcess::readyReadStandardOutput,this, &MainWindow::readFromStdout);
742742
#if QT_VERSION < QT_VERSION_CHECK (5, 6, 0)
743743
connect(&proc_jtdxjt9, static_cast<void (QProcess::*) (QProcess::ProcessError)> (&QProcess::error),
744744
[this] (QProcess::ProcessError error) {
@@ -759,7 +759,7 @@ MainWindow::MainWindow(bool multiple, QSettings * settings, QSharedMemory *shdme
759759
}
760760
});
761761

762-
connect(&p1, SIGNAL(readyReadStandardOutput()),this, SLOT(p1ReadFromStdout()));
762+
connect(&p1, &QProcess::readyReadStandardOutput,this, &MainWindow::p1ReadFromStdout);
763763
#if QT_VERSION < QT_VERSION_CHECK (5, 6, 0)
764764
connect(&p1, static_cast<void (QProcess::*) (QProcess::ProcessError)> (&QProcess::error),
765765
[this] (QProcess::ProcessError error) {

mainwindow.h

+4-3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <QFileSystemWatcher>
2525
#include <QClipboard>
2626

27+
#include "NonInheritingProcess.hpp"
2728
#include "AudioDevice.hpp"
2829
#include "commons.h"
2930
#include "Radio.hpp"
@@ -639,9 +640,9 @@ private slots:
639640

640641
QFileSystemWatcher *fsWatcher;
641642

642-
QProcess proc_jtdxjt9;
643-
QProcess p1;
644-
QProcess p3;
643+
NonInheritingProcess proc_jtdxjt9;
644+
NonInheritingProcess p1;
645+
NonInheritingProcess p3;
645646

646647
WSPRNet *wsprNet;
647648
EQSL *Eqsl;

0 commit comments

Comments
 (0)