ProcessManager.hpp 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. #pragma once
  2. # ifndef PROCESS_H
  3. # define PROCESS_H
  4. #define OBJPREFIX LR"(Local\)"
  5. constexpr size_t guidSize = std::size(L"{00000000-0000-0000-0000-000000000000}"); // including the null terminator
  6. constexpr size_t prefixSize = std::size(OBJPREFIX) - 1;
  7. constexpr size_t objNameSize = prefixSize + guidSize;
  8. struct ProcessInfo
  9. {
  10. DWORD processId;
  11. DWORD threadId;
  12. HWND hWndConsole;
  13. };
  14. namespace ProcessManager
  15. {
  16. enum struct State
  17. {
  18. Stop,
  19. Running,
  20. WaitStop
  21. };
  22. namespace {
  23. State _state = State::Stop;
  24. std::filesystem::path _exePath;
  25. std::filesystem::path _homeDir;
  26. std::filesystem::path _configFile;
  27. std::wstring _ctlAddr, _ctlSecret, _clashCmd;
  28. wil::unique_handle _hJob;
  29. wil::unique_process_information _subProcInfo, _clashProcInfo;
  30. HWND _hWndConsole = nullptr;
  31. wil::unique_handle _hEvent;
  32. HINSTANCE mInstance;
  33. void _UpdateClashCmd()
  34. {
  35. _clashCmd.assign(LR"(")");
  36. _clashCmd.append(_exePath.filename());
  37. _clashCmd.append(LR"(")");
  38. if (!_homeDir.empty())
  39. {
  40. _clashCmd.append(LR"( -d ")");
  41. _clashCmd.append(_homeDir);
  42. _clashCmd.append(LR"(")");
  43. }
  44. if (!_configFile.empty())
  45. {
  46. _clashCmd.append(LR"( -f ")");
  47. _clashCmd.append(_configFile);
  48. _clashCmd.append(LR"(")");
  49. }
  50. //if (!_uiDir.empty())
  51. //{
  52. // _clashCmd.append(LR"( -ext-ui ")");
  53. // _clashCmd.append(_uiDir);
  54. // _clashCmd.append(LR"(")");
  55. //}
  56. //if (!_ctlAddr.empty())
  57. //{
  58. // _clashCmd.append(LR"( -ext-ctl ")");
  59. // _clashCmd.append(_ctlAddr);
  60. // _clashCmd.append(LR"(")");
  61. //}
  62. //// Override secret even if empty
  63. //_clashCmd.append(LR"( -secret ")");
  64. //_clashCmd.append(_ctlSecret);
  65. //_clashCmd.append(LR"(")");
  66. }
  67. }
  68. inline void SetInsTanCe(HINSTANCE m) {
  69. mInstance = m;
  70. }
  71. void SetArgs(std::filesystem::path exePath, std::filesystem::path homeDir, std::filesystem::path cconfigFile)
  72. {
  73. _exePath = exePath;
  74. _homeDir = homeDir;
  75. _configFile = cconfigFile;
  76. //_uiDir = "";
  77. //_ctlAddr = L"";
  78. //_ctlSecret = L"";
  79. _UpdateClashCmd();
  80. }
  81. void SetConfigFile(std::filesystem::path configFile)
  82. {
  83. _configFile = configFile;
  84. _UpdateClashCmd();
  85. }
  86. inline State IsRunning() { return _state; }
  87. inline const PROCESS_INFORMATION& GetSubProcessInfo() { return _subProcInfo; }
  88. inline const PROCESS_INFORMATION& GetClashProcessInfo() { return _clashProcInfo; }
  89. inline HWND GetConsoleWindow() { return _hWndConsole; }
  90. inline bool Start()
  91. {
  92. if (_state != State::Stop)
  93. return false;
  94. try
  95. {
  96. _hJob.reset(CreateJobObjectW(nullptr, nullptr));
  97. THROW_LAST_ERROR_IF_NULL(_hJob);
  98. JOBOBJECT_BASIC_LIMIT_INFORMATION bli;
  99. bli.LimitFlags = JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION | JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
  100. JOBOBJECT_EXTENDED_LIMIT_INFORMATION eli;
  101. eli.BasicLimitInformation = bli;
  102. THROW_IF_WIN32_BOOL_FALSE(SetInformationJobObject(_hJob.get(), JobObjectExtendedLimitInformation, &eli, sizeof(eli)));
  103. wchar_t objName[objNameSize + 1] = OBJPREFIX;
  104. GUID guid = {};
  105. THROW_IF_FAILED(CoCreateGuid(&guid));
  106. THROW_HR_IF(E_OUTOFMEMORY, StringFromGUID2(guid, objName + prefixSize, guidSize) != guidSize);
  107. size_t s = (_exePath.native().size() + 1) + (_clashCmd.size() + 1) * sizeof(wchar_t);
  108. size_t l = sizeof(ProcessInfo);
  109. size_t size = max(s, l);
  110. objName[objNameSize - 1] = L'F';
  111. wil::unique_handle hFileMapping(CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, static_cast<DWORD>(size), objName));
  112. THROW_LAST_ERROR_IF_NULL(hFileMapping);
  113. auto error = GetLastError();
  114. if (error == ERROR_ALREADY_EXISTS) THROW_WIN32(error);
  115. wil::unique_mapview_ptr<wchar_t> buffer(reinterpret_cast<wchar_t*>(MapViewOfFile(hFileMapping.get(), FILE_MAP_WRITE | FILE_MAP_READ, 0, 0, 0)));
  116. THROW_LAST_ERROR_IF_NULL(buffer);
  117. objName[objNameSize - 1] = L'E';
  118. wil::unique_handle hEvent(CreateEventW(nullptr, FALSE, FALSE, objName));
  119. THROW_LAST_ERROR_IF_NULL(hEvent);
  120. error = GetLastError();
  121. if (error == ERROR_ALREADY_EXISTS) THROW_WIN32(error);
  122. auto exePathPtr = buffer.get();
  123. size_t i = _exePath.native().copy(exePathPtr, std::filesystem::path::string_type::npos);
  124. exePathPtr[i] = 0;
  125. auto cmdPtr = buffer.get() + i + 1;
  126. i = _clashCmd.copy(cmdPtr, std::wstring::npos);
  127. cmdPtr[i] = 0;
  128. auto selfPath = GetModuleFsPath(mInstance);
  129. auto guidStr = objName + prefixSize;
  130. std::wstring cmd(LR"(")");
  131. cmd.append(selfPath);
  132. cmd.append(LR"(" --pm=)");
  133. cmd.append(guidStr, guidSize - 1);
  134. {
  135. // Disable Windows Error Reporting for subprocess
  136. auto lastErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
  137. auto restoreErrorMode = wil::scope_exit([=]() { // Restore error mode after CreateProcessW
  138. SetErrorMode(lastErrorMode);
  139. });
  140. wil::unique_cotaskmem_string appId;
  141. THROW_IF_FAILED(GetCurrentProcessExplicitAppUserModelID(&appId));
  142. /*wil::unique_cotaskmem_string appId;
  143. THROW_IF_FAILED(GetCurrentProcessExplicitAppUserModelID(&appId));*/
  144. /*STARTUPINFOW si = {
  145. .cb = sizeof(si),
  146. .lpTitle = appId.get(),
  147. .dwFlags = STARTF_FORCEOFFFEEDBACK | STARTF_PREVENTPINNING | STARTF_TITLEISAPPID | STARTF_USESHOWWINDOW,
  148. .wShowWindow = SW_HIDE
  149. };*/
  150. STARTUPINFOW si;
  151. si.cb = sizeof(si);
  152. si.lpTitle = appId.get();
  153. si.dwFlags = STARTF_FORCEOFFFEEDBACK | STARTF_PREVENTPINNING | STARTF_TITLEISAPPID | STARTF_USESHOWWINDOW;
  154. si.wShowWindow = SW_HIDE;
  155. THROW_IF_WIN32_BOOL_FALSE(CreateProcessW(selfPath.c_str(), cmd.data(), nullptr, nullptr, FALSE, CREATE_SUSPENDED, nullptr, nullptr, &si, &_subProcInfo));
  156. }
  157. // Ensure process in job before start
  158. THROW_IF_WIN32_BOOL_FALSE(AssignProcessToJobObject(_hJob.get(), _subProcInfo.hProcess));
  159. THROW_LAST_ERROR_IF(ResumeThread(_subProcInfo.hThread) == static_cast<DWORD>(-1));
  160. HANDLE handles[] = { hEvent.get(), _subProcInfo.hProcess };
  161. auto ret = WaitForMultipleObjects(static_cast<DWORD>(std::size(handles)), handles, FALSE, INFINITE);
  162. error = ERROR_TIMEOUT;
  163. switch (ret)
  164. {
  165. case WAIT_OBJECT_0: // Event signaled, clash process started suspended
  166. {
  167. auto info = reinterpret_cast<ProcessInfo*>(buffer.get());
  168. _clashProcInfo.dwProcessId = info->processId;
  169. _clashProcInfo.hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, _clashProcInfo.dwProcessId);
  170. THROW_LAST_ERROR_IF_NULL(_clashProcInfo.hProcess);
  171. _clashProcInfo.dwThreadId = info->threadId;
  172. _clashProcInfo.hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, _clashProcInfo.dwThreadId);
  173. THROW_LAST_ERROR_IF_NULL(_clashProcInfo.hThread);
  174. _hWndConsole = info->hWndConsole;
  175. }
  176. break;
  177. case WAIT_OBJECT_0 + 1: // Sub process exited before event signaled
  178. {
  179. DWORD exitCode;
  180. THROW_IF_WIN32_BOOL_FALSE(GetExitCodeProcess(_subProcInfo.hProcess, &exitCode));
  181. HRESULT hr = static_cast<HRESULT>(exitCode); // Treat exit code as hresult
  182. if (SUCCEEDED(hr)) hr = E_UNEXPECTED;
  183. THROW_HR(hr);
  184. }
  185. return false;
  186. case WAIT_FAILED:
  187. error = GetLastError();
  188. [[fallthrough]];
  189. case WAIT_TIMEOUT:
  190. THROW_WIN32(error);
  191. return false;
  192. }
  193. _hEvent = std::move(hEvent);
  194. ResumeThread(_clashProcInfo.hThread);
  195. }
  196. catch (...)
  197. {
  198. _hJob.reset();
  199. LOG_CAUGHT_EXCEPTION();
  200. return false;
  201. }
  202. _state = State::Running;
  203. return true;
  204. }
  205. inline void Stop()
  206. {
  207. if (_state != State::Stop)
  208. {
  209. _state = State::Stop;
  210. _hJob.reset();
  211. _subProcInfo.reset();
  212. _clashProcInfo.reset();
  213. _hWndConsole = nullptr;
  214. _hEvent.reset();
  215. }
  216. }
  217. inline void SendStopSignal()
  218. {
  219. if (_state == State::Running)
  220. {
  221. _state = State::WaitStop;
  222. SetEvent(_hEvent.get());
  223. }
  224. }
  225. inline int SubProcess(std::wstring_view guid)
  226. {
  227. try
  228. {
  229. wchar_t objName[objNameSize + 1] = OBJPREFIX;
  230. size_t i = guid.copy(objName + prefixSize, guidSize - 1);
  231. objName[prefixSize + i] = L'F';
  232. wil::unique_handle hFileMapping(OpenFileMappingW(FILE_MAP_WRITE | FILE_MAP_READ, FALSE, objName));
  233. THROW_LAST_ERROR_IF_NULL(hFileMapping);
  234. wil::unique_mapview_ptr<wchar_t> buffer(reinterpret_cast<wchar_t*>(MapViewOfFile(hFileMapping.get(), FILE_MAP_WRITE | FILE_MAP_READ, 0, 0, 0)));
  235. THROW_LAST_ERROR_IF_NULL(buffer);
  236. objName[prefixSize + i] = L'E';
  237. wil::unique_handle hEvent(OpenEventW(EVENT_MODIFY_STATE | SYNCHRONIZE, FALSE, objName));
  238. THROW_LAST_ERROR_IF_NULL(hEvent);
  239. THROW_IF_WIN32_BOOL_FALSE(AllocConsole());
  240. HWND hWndConsole = ::GetConsoleWindow();
  241. THROW_LAST_ERROR_IF_NULL(hWndConsole);
  242. ShowWindow(hWndConsole, SW_HIDE);
  243. auto exePath = buffer.get();
  244. auto clashCmd = buffer.get() + wcslen(exePath) + 1;
  245. STARTUPINFOW si;
  246. si.cb = sizeof(si);
  247. wil::unique_process_information procInfo;
  248. THROW_IF_WIN32_BOOL_FALSE(CreateProcessW(exePath, clashCmd, nullptr, nullptr, FALSE, CREATE_SUSPENDED, nullptr, nullptr, &si, &procInfo));
  249. auto info = reinterpret_cast<ProcessInfo*>(buffer.get());
  250. /**info = {
  251. .processId = procInfo.dwProcessId,
  252. .threadId = procInfo.dwThreadId,
  253. .hWndConsole = hWndConsole
  254. };*/
  255. info->processId = procInfo.dwProcessId;
  256. info->threadId = procInfo.dwThreadId;
  257. info->hWndConsole = hWndConsole;
  258. SetConsoleCtrlHandler(nullptr, TRUE); // Ignores Ctrl+C
  259. THROW_IF_WIN32_BOOL_FALSE(SetEvent(hEvent.get()));
  260. while (true)
  261. {
  262. HANDLE handles[] = { hEvent.get(), procInfo.hProcess };
  263. auto ret = WaitForMultipleObjects(static_cast<DWORD>(std::size(handles)), handles, FALSE, INFINITE);
  264. if (ret == WAIT_OBJECT_0) // Event signaled
  265. {
  266. GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
  267. }
  268. else if (ret == WAIT_OBJECT_0 + 1) // Clash process exited
  269. {
  270. break;
  271. }
  272. else if (ret == WAIT_FAILED)
  273. {
  274. THROW_LAST_ERROR();
  275. }
  276. else if (ret == WAIT_TIMEOUT)
  277. {
  278. THROW_WIN32(ERROR_TIMEOUT);
  279. }
  280. }
  281. const std::wstring_view msg = (
  282. L"\n"
  283. L"[Process completed]\n");
  284. THROW_IF_WIN32_BOOL_FALSE(WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), msg.data(), static_cast<DWORD>(msg.size()), nullptr, nullptr));
  285. static_cast<void>(_getch());
  286. }
  287. CATCH_RETURN();
  288. return S_OK;
  289. }
  290. }
  291. #undef OBJPREFIX
  292. # endif