1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2012-2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include "nel/misc/win_thread.h"
25 #include "nel/misc/path.h"
39 CWinThread
MainThread ((void*)GetCurrentThread (), GetCurrentThreadId());
40 DWORD TLSThreadPointer
= 0xFFFFFFFF;
42 // the IThread static creator
43 IThread
*IThread::create (IRunnable
*runnable
, uint32 stackSize
)
45 return new CWinThread (runnable
, stackSize
);
48 IThread
*IThread::getCurrentThread ()
50 // TLS alloc must have been done
51 nlassert (TLSThreadPointer
!= 0xffffffff);
53 // Get the thread pointer
54 IThread
*thread
= (IThread
*)TlsGetValue (TLSThreadPointer
);
56 // Return current thread
60 static unsigned long __stdcall
ProxyFunc (void *arg
)
62 CWinThread
*parent
= (CWinThread
*) arg
;
64 // TLS alloc must have been done
65 nlassert (TLSThreadPointer
!= 0xffffffff);
67 // Set the thread pointer in TLS memory
68 nlverify (TlsSetValue (TLSThreadPointer
, (void*)parent
) != 0);
70 // Attach exception handler
71 attachExceptionHandler();
74 parent
->Runnable
->run();
79 CWinThread::CWinThread (IRunnable
*runnable
, uint32 stackSize
)
81 _StackSize
= stackSize
;
82 this->Runnable
= runnable
;
89 class CWinCriticalSection
94 CWinCriticalSection() { InitializeCriticalSection(&cs
); }
95 ~CWinCriticalSection() { DeleteCriticalSection(&cs
); }
96 inline void enter() { EnterCriticalSection(&cs
); }
97 inline void leave() { LeaveCriticalSection(&cs
); }
99 CWinCriticalSection s_CS
;
100 }/* anonymous namespace */
102 CWinThread::CWinThread (void* threadHandle
, uint32 threadId
)
106 this->Runnable
= NULL
;
107 ThreadHandle
= threadHandle
;
110 // TLS alloc must have been done
111 TLSThreadPointer
= TlsAlloc ();
112 nlassert (TLSThreadPointer
!=0xffffffff);
114 // Set the thread pointer in TLS memory
115 nlverify (TlsSetValue (TLSThreadPointer
, (void*)this) != 0);
117 if (GetCurrentThreadId() == threadId
)
119 _SuspendCount
= 0; // is calling thread call this itself, well, if we reach this place
120 // there are chances that it is not suspended ...
124 // initialized from another thread (very unlikely ...)
125 nlassert(0); // WARNING: following code has not tested! don't know if it work fo real ...
126 // This is just a suggestion of a possible solution, should this situation one day occur ...
127 // Ensure that this thread don't get deleted, or we could suspend the main thread
129 // the 2 following statement must be executed atomicaly among the threads of the current process !
130 SuspendThread(threadHandle
);
131 _SuspendCount
= ResumeThread(threadHandle
);
137 void CWinThread::incSuspendCount()
139 nlassert(ThreadHandle
); // start was not called !!
140 int newSuspendCount
= ::SuspendThread(ThreadHandle
) + 1;
141 nlassert(newSuspendCount
!= 0xffffffff); // more infos with 'GetLastError'
142 nlassert(newSuspendCount
== _SuspendCount
+ 1); // is this assert fire , then 'SuspendThread' or 'ResumeThread'
143 // have been called outside of this object interface! (on this thread handle ...)
144 _SuspendCount
= newSuspendCount
;
147 void CWinThread::decSuspendCount()
149 nlassert(ThreadHandle
); // 'start' was not called !!
150 nlassert(_SuspendCount
> 0);
151 int newSuspendCount
= ::ResumeThread(ThreadHandle
) - 1;
152 nlassert(newSuspendCount
!= 0xffffffff); // more infos with 'GetLastError'
153 nlassert(newSuspendCount
== _SuspendCount
- 1); // is this assert fire , then 'SuspendThread' or 'ResumeThread'
154 // have been called outside of this object interface! (on this thread handle ...)
155 _SuspendCount
= newSuspendCount
;
158 void CWinThread::suspend()
160 if (getSuspendCount() == 0)
166 void CWinThread::resume()
168 while (getSuspendCount() != 0)
174 void CWinThread::setPriority(TThreadPriority priority
)
176 nlassert(ThreadHandle
); // 'start' was not called !!
177 BOOL result
= SetThreadPriority(ThreadHandle
, (int)priority
);
181 void CWinThread::enablePriorityBoost(bool enabled
)
183 nlassert(ThreadHandle
); // 'start' was not called !!
184 SetThreadPriorityBoost(ThreadHandle
, enabled
? TRUE
: FALSE
);
188 CWinThread::~CWinThread ()
190 // If not the main thread
194 nlassert (TLSThreadPointer
!=0xffffffff);
195 TlsFree (TLSThreadPointer
);
199 if (ThreadHandle
!= NULL
) terminate();
203 void CWinThread::start ()
206 throw EThread("Starting a thread that is already started, existing thread will continue running, this should not happen");
208 // ThreadHandle = (void *) ::CreateThread (NULL, _StackSize, ProxyFunc, this, 0, (DWORD *)&ThreadId);
209 ThreadHandle
= (void *) ::CreateThread (NULL
, 0, ProxyFunc
, this, 0, (DWORD
*)&ThreadId
);
210 // nldebug("NLMISC: thread %x started for runnable '%x'", typeid( Runnable ).name());
211 // OutputDebugString(toString(NL_LOC_MSG " NLMISC: thread %x started for runnable '%s'\n", ThreadId, typeid( *Runnable ).name()).c_str());
212 if (ThreadHandle
== NULL
)
214 throw EThread ( "Cannot create new thread" );
216 SetThreadPriorityBoost (ThreadHandle
, TRUE
); // FALSE == Enable Priority Boost
221 bool CWinThread::isRunning()
223 if (ThreadHandle
== NULL
)
227 if (!GetExitCodeThread(ThreadHandle
, &exitCode
))
230 return exitCode
== STILL_ACTIVE
;
234 void CWinThread::terminate ()
236 TerminateThread((HANDLE
)ThreadHandle
, 0);
237 CloseHandle((HANDLE
)ThreadHandle
);
242 void CWinThread::wait ()
244 if (ThreadHandle
== NULL
) return;
246 WaitForSingleObject(ThreadHandle
, INFINITE
);
247 CloseHandle(ThreadHandle
);
252 bool CWinThread::setCPUMask(uint64 cpuMask
)
255 if (ThreadHandle
== NULL
)
258 // Ask the system for number of processor available for this process
259 return SetThreadAffinityMask ((HANDLE
)ThreadHandle
, (DWORD_PTR
)cpuMask
) != 0;
262 uint64
CWinThread::getCPUMask()
265 if (ThreadHandle
== NULL
)
268 // Get the current process mask
269 uint64 mask
=IProcess::getCurrentProcess ()->getCPUMask ();
271 // Get thread affinity mask
272 DWORD_PTR old
= SetThreadAffinityMask ((HANDLE
)ThreadHandle
, (DWORD_PTR
)mask
);
278 SetThreadAffinityMask ((HANDLE
)ThreadHandle
, old
);
284 std::string
CWinThread::getUserName()
286 wchar_t userName
[512];
288 GetUserNameW (userName
, &size
);
289 return wideToUtf8(userName
);
294 // The current process
295 CWinProcess
CurrentProcess ((void*)GetCurrentProcess());
297 // Get the current process
298 IProcess
*IProcess::getCurrentProcess ()
300 return &CurrentProcess
;
303 CWinProcess::CWinProcess (void *handle
)
305 // Get the current process handle
306 _ProcessHandle
= handle
;
309 uint64
CWinProcess::getCPUMask()
311 // Ask the system for number of processor available for this process
312 DWORD_PTR processAffinityMask
;
313 DWORD_PTR systemAffinityMask
;
314 if (GetProcessAffinityMask((HANDLE
)_ProcessHandle
, &processAffinityMask
, &systemAffinityMask
))
316 // Return the CPU mask
317 return (uint64
)processAffinityMask
;
323 bool CWinProcess::setCPUMask(uint64 mask
)
325 // Ask the system for number of processor available for this process
326 DWORD_PTR processAffinityMask
= (DWORD_PTR
)mask
;
327 return SetProcessAffinityMask((HANDLE
)_ProcessHandle
, processAffinityMask
)!=0;
330 // ****************************************************************************************************************
332 * Simple wrapper around the PSAPI library
333 * \author Nicolas Vizerie
341 typedef BOOL (WINAPI
*EnumProcessesFunPtr
)(DWORD
*lpidProcess
, DWORD cb
, DWORD
*cbNeeded
);
342 typedef DWORD (WINAPI
*GetModuleFileNameExWFunPtr
)(HANDLE hProcess
, HMODULE hModule
, LPWSTR lpFilename
, DWORD nSize
);
343 typedef BOOL (WINAPI
*EnumProcessModulesFunPtr
)(HANDLE hProcess
, HMODULE
*lphModule
, DWORD cb
, LPDWORD lpcbNeeded
);
344 EnumProcessesFunPtr EnumProcesses
;
345 GetModuleFileNameExWFunPtr GetModuleFileNameExW
;
346 EnumProcessModulesFunPtr EnumProcessModules
;
352 HINSTANCE _PSAPILibHandle
;
356 // ****************************************************************************************************************
357 CPSAPILib::CPSAPILib()
360 _PSAPILibHandle
= NULL
;
361 EnumProcesses
= NULL
;
362 GetModuleFileNameExW
= NULL
;
363 EnumProcessModules
= NULL
;
366 // ****************************************************************************************************************
367 CPSAPILib::~CPSAPILib()
371 FreeLibrary(_PSAPILibHandle
);
375 // ****************************************************************************************************************
376 bool CPSAPILib::init()
379 if (_LoadFailed
) return false;
380 if (!_PSAPILibHandle
)
382 _PSAPILibHandle
= LoadLibraryA("psapi.dll");
383 if (!_PSAPILibHandle
)
385 nlwarning("couldn't load psapi.dll, possibly not supported by os");
389 EnumProcesses
= (EnumProcessesFunPtr
) GetProcAddress(_PSAPILibHandle
, "EnumProcesses");
390 GetModuleFileNameExW
= (GetModuleFileNameExWFunPtr
) GetProcAddress(_PSAPILibHandle
, "GetModuleFileNameExW");
391 EnumProcessModules
= (EnumProcessModulesFunPtr
) GetProcAddress(_PSAPILibHandle
, "EnumProcessModules");
392 if (!EnumProcesses
||
393 !GetModuleFileNameExW
||
397 nlwarning("Failed to import functions from psapi.dll!");
406 static CPSAPILib PSAPILib
;
410 // ****************************************************************************************************************
411 bool CWinProcess::enumProcessesId(std::vector
<uint32
> &processesId
)
413 if (!PSAPILib
.init()) return false;
415 std::vector
<uint32
> prcIds(16);
419 if (!PSAPILib
.EnumProcesses((DWORD
*) &prcIds
[0], (DWORD
)(prcIds
.size() * sizeof(DWORD
)), &cbNeeded
))
421 nlwarning("Processes enumeration failed!");
424 if (cbNeeded
< prcIds
.size() * sizeof(DWORD
))
426 prcIds
.resize(cbNeeded
/ sizeof(DWORD
));
429 // make some more room
430 prcIds
.resize(prcIds
.size() * 2);
432 processesId
.swap(prcIds
);
436 // ****************************************************************************************************************
437 bool CWinProcess::enumProcessModules(uint32 processId
, std::vector
<std::string
> &moduleNames
)
439 if (!PSAPILib
.init()) return false;
440 HANDLE hProcess
= OpenProcess(PROCESS_QUERY_INFORMATION
|PROCESS_VM_READ
, FALSE
, (DWORD
) processId
);
441 if (!hProcess
) return false;
443 std::vector
<HMODULE
> prcModules(2);
447 if (!PSAPILib
.EnumProcessModules(hProcess
, (HMODULE
*) &prcModules
[0], (DWORD
)(prcModules
.size() * sizeof(HMODULE
)), &cbNeeded
))
449 //nlwarning("Processe modules enumeration failed!");
452 if (cbNeeded
< prcModules
.size() * sizeof(HMODULE
))
454 prcModules
.resize(cbNeeded
/ sizeof(HMODULE
));
457 // make some more room
458 prcModules
.resize(prcModules
.size() * 2);
461 std::vector
<std::string
> resultModuleNames
;
462 wchar_t moduleName
[MAX_PATH
+ 1];
463 for (uint m
= 0; m
< prcModules
.size(); ++m
)
465 if (PSAPILib
.GetModuleFileNameExW(hProcess
, prcModules
[m
], moduleName
, MAX_PATH
))
467 moduleNames
.push_back(wideToUtf8(moduleName
));
470 CloseHandle(hProcess
);
474 // ****************************************************************************************************************
475 uint32
CWinProcess::getProcessIdFromModuleFilename(const std::string
&moduleFileName
)
477 std::vector
<uint32
> processesId
;
478 if (!enumProcessesId(processesId
)) return false;
479 std::vector
<std::string
> moduleNames
;
480 for (uint prc
= 0; prc
< processesId
.size(); ++prc
)
482 if (enumProcessModules(processesId
[prc
], moduleNames
))
484 for (uint m
= 0; m
< moduleNames
.size(); ++m
)
486 if (nlstricmp(CFile::getFilename(moduleNames
[m
]), moduleFileName
) == 0)
488 return processesId
[prc
];
496 // ****************************************************************************************************************
497 bool CWinProcess::terminateProcess(uint32 processId
, uint exitCode
)
499 if (!processId
) return false;
500 HANDLE hProcess
= OpenProcess(PROCESS_TERMINATE
, FALSE
, (DWORD
) processId
);
501 if (!hProcess
) return false;
502 BOOL ok
= TerminateProcess(hProcess
, (UINT
) exitCode
);
503 CloseHandle(hProcess
);
507 // ****************************************************************************************************************
508 bool CWinProcess::terminateProcessFromModuleName(const std::string
&moduleName
, uint exitCode
)
510 return terminateProcess(getProcessIdFromModuleFilename(moduleName
), exitCode
);
521 // I didn't use and test that code, eventually, but maybe useful in the future
523 class CProcessWatchTask : public IRunnable
528 CProcessWatchTask(HANDLE hProcess) : HProcess(hProcess)
533 WaitForSingleObject(HProcess, INFINITE);
537 class CProcessWatchImpl
541 IThread *WatchThread;
542 CProcessWatchTask *WatchTask;
544 CProcessWatchImpl() : Launched(false), WatchThread(NULL), WatchTask(NULL)
555 if (WatchThread->isRunning())
557 WatchThread->terminate();
566 bool launch(const std::string &programName, const std::string &arguments)
568 if (isRunning()) return false;
569 PROCESS_INFORMATION processInfo;
570 STARTUPINFO startupInfo = {0};
571 startupInfo.cb = sizeof(STARTUPINFO);
572 if (CreateProcessW(programName.c_str(), const_cast<LPTSTR>(arguments.c_str()), NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo))
574 WatchTask = new CProcessWatchTask(processInfo.hProcess);
575 WatchThread = IThread::create(WatchTask);
576 WatchThread->start();
584 if (!Launched) return false;
585 nlassert(WatchThread);
587 if (WatchThread->isRunning()) return true;
594 CProcessWatch::CProcessWatch()
596 _PImpl = new CProcessWatchImpl;
599 CProcessWatch::~CProcessWatch()
604 bool CProcessWatch::launch(const std::string &programName, const std::string &arguments)
606 return _PImpl->launch(programName, arguments);
609 bool CProcessWatch::isRunning() const
611 return _PImpl->isRunning();
617 #endif // NL_OS_WINDOWS