CArchMultithreadWindows.cpp

00001 /*
00002  * synergy -- mouse and keyboard sharing utility
00003  * Copyright (C) 2002 Chris Schoeneman
00004  * 
00005  * This package is free software you can redistribute it and/or
00006  * modify it under the terms of the GNU General Public License
00007  * found in the file COPYING that should have accompanied this file.
00008  * 
00009  * This package is distributed in the hope that it will be useful,
00010  * but WITHOUT ANY WARRANTY without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  * GNU General Public License for more details.
00013  */
00014 
00015 #if defined(_MSC_VER) && !defined(_MT)
00016 #   error multithreading compile option is required
00017 #endif
00018 
00019 #include "CArchMultithreadWindows.h"
00020 #include "CArch.h"
00021 #include "XArch.h"
00022 #include <process.h>
00023 
00024 //
00025 // note -- implementation of condition variable taken from:
00026 //   http://www.cs.wustl.edu/~schmidt/win32-cv-1.html
00027 // titled "Strategies for Implementing POSIX Condition Variables
00028 // on Win32."  it also provides an implementation that doesn't
00029 // suffer from the incorrectness problem described in our
00030 // corresponding header but it is slower, still unfair, and
00031 // can cause busy waiting.
00032 //
00033 
00034 //
00035 // CArchThreadImpl
00036 //
00037 
00038 class CArchThreadImpl {
00039 public:
00040     CArchThreadImpl();
00041     ~CArchThreadImpl();
00042 
00043 public:
00044     int                 m_refCount;
00045     HANDLE              m_thread;
00046     DWORD               m_id;
00047     IArchMultithread::ThreadFunc    m_func;
00048     void*               m_userData;
00049     HANDLE              m_cancel;
00050     bool                m_cancelling;
00051     HANDLE              m_exit;
00052     void*               m_result;
00053     void*               m_networkData;
00054 };
00055 
00056 CArchThreadImpl::CArchThreadImpl() :
00057     m_refCount(1),
00058     m_thread(NULL),
00059     m_id(0),
00060     m_func(NULL),
00061     m_userData(NULL),
00062     m_cancelling(false),
00063     m_result(NULL),
00064     m_networkData(NULL)
00065 {
00066     m_exit   = CreateEvent(NULL, TRUE, FALSE, NULL);
00067     m_cancel = CreateEvent(NULL, TRUE, FALSE, NULL);
00068 }
00069 
00070 CArchThreadImpl::~CArchThreadImpl()
00071 {
00072     CloseHandle(m_exit);
00073     CloseHandle(m_cancel);
00074 }
00075 
00076 
00077 //
00078 // CArchMultithreadWindows
00079 //
00080 
00081 CArchMultithreadWindows*    CArchMultithreadWindows::s_instance = NULL;
00082 
00083 CArchMultithreadWindows::CArchMultithreadWindows()
00084 {
00085     assert(s_instance == NULL);
00086     s_instance = this;
00087 
00088     // no signal handlers
00089     for (size_t i = 0; i < kNUM_SIGNALS; ++i) {
00090         m_signalFunc[i]     = NULL;
00091         m_signalUserData[i] = NULL;
00092     }
00093 
00094     // create mutex for thread list
00095     m_threadMutex = newMutex();
00096 
00097     // create thread for calling (main) thread and add it to our
00098     // list.  no need to lock the mutex since we're the only thread.
00099     m_mainThread           = new CArchThreadImpl;
00100     m_mainThread->m_thread = NULL;
00101     m_mainThread->m_id     = GetCurrentThreadId();
00102     insert(m_mainThread);
00103 }
00104 
00105 CArchMultithreadWindows::~CArchMultithreadWindows()
00106 {
00107     s_instance = NULL;
00108 
00109     // clean up thread list
00110     for (CThreadList::iterator index  = m_threadList.begin();
00111                                index != m_threadList.end(); ++index) {
00112         delete *index;
00113     }
00114 
00115     // done with mutex
00116     delete m_threadMutex;
00117 }
00118 
00119 void
00120 CArchMultithreadWindows::setNetworkDataForCurrentThread(void* data)
00121 {
00122     lockMutex(m_threadMutex);
00123     CArchThreadImpl* thread = findNoRef(GetCurrentThreadId());
00124     thread->m_networkData = data;
00125     unlockMutex(m_threadMutex);
00126 }
00127 
00128 void*
00129 CArchMultithreadWindows::getNetworkDataForThread(CArchThread thread)
00130 {
00131     lockMutex(m_threadMutex);
00132     void* data = thread->m_networkData;
00133     unlockMutex(m_threadMutex);
00134     return data;
00135 }
00136 
00137 HANDLE
00138 CArchMultithreadWindows::getCancelEventForCurrentThread()
00139 {
00140     lockMutex(m_threadMutex);
00141     CArchThreadImpl* thread = findNoRef(GetCurrentThreadId());
00142     unlockMutex(m_threadMutex);
00143     return thread->m_cancel;
00144 }
00145 
00146 CArchMultithreadWindows*
00147 CArchMultithreadWindows::getInstance()
00148 {
00149     return s_instance;
00150 }
00151 
00152 CArchCond
00153 CArchMultithreadWindows::newCondVar()
00154 {
00155     CArchCondImpl* cond                       = new CArchCondImpl;
00156     cond->m_events[CArchCondImpl::kSignal]    = CreateEvent(NULL,
00157                                                     FALSE, FALSE, NULL);
00158     cond->m_events[CArchCondImpl::kBroadcast] = CreateEvent(NULL,
00159                                                     TRUE,  FALSE, NULL);
00160     cond->m_waitCountMutex                    = newMutex();
00161     cond->m_waitCount                         = 0;
00162     return cond;
00163 }
00164 
00165 void
00166 CArchMultithreadWindows::closeCondVar(CArchCond cond)
00167 {
00168     CloseHandle(cond->m_events[CArchCondImpl::kSignal]);
00169     CloseHandle(cond->m_events[CArchCondImpl::kBroadcast]);
00170     closeMutex(cond->m_waitCountMutex);
00171     delete cond;
00172 }
00173 
00174 void
00175 CArchMultithreadWindows::signalCondVar(CArchCond cond)
00176 {
00177     // is anybody waiting?
00178     lockMutex(cond->m_waitCountMutex);
00179     const bool hasWaiter = (cond->m_waitCount > 0);
00180     unlockMutex(cond->m_waitCountMutex);
00181 
00182     // wake one thread if anybody is waiting
00183     if (hasWaiter) {
00184         SetEvent(cond->m_events[CArchCondImpl::kSignal]);
00185     }
00186 }
00187 
00188 void
00189 CArchMultithreadWindows::broadcastCondVar(CArchCond cond)
00190 {
00191     // is anybody waiting?
00192     lockMutex(cond->m_waitCountMutex);
00193     const bool hasWaiter = (cond->m_waitCount > 0);
00194     unlockMutex(cond->m_waitCountMutex);
00195 
00196     // wake all threads if anybody is waiting
00197     if (hasWaiter) {
00198         SetEvent(cond->m_events[CArchCondImpl::kBroadcast]);
00199     }
00200 }
00201 
00202 bool
00203 CArchMultithreadWindows::waitCondVar(CArchCond cond,
00204                             CArchMutex mutex, double timeout)
00205 {
00206     // prepare to wait
00207     const DWORD winTimeout = (timeout < 0.0) ? INFINITE :
00208                                 static_cast<DWORD>(1000.0 * timeout);
00209 
00210     // make a list of the condition variable events and the cancel event
00211     // for the current thread.
00212     HANDLE handles[4];
00213     handles[0] = cond->m_events[CArchCondImpl::kSignal];
00214     handles[1] = cond->m_events[CArchCondImpl::kBroadcast];
00215     handles[2] = getCancelEventForCurrentThread();
00216 
00217     // update waiter count
00218     lockMutex(cond->m_waitCountMutex);
00219     ++cond->m_waitCount;
00220     unlockMutex(cond->m_waitCountMutex);
00221 
00222     // release mutex.  this should be atomic with the wait so that it's
00223     // impossible for another thread to signal us between the unlock and
00224     // the wait, which would lead to a lost signal on broadcasts.
00225     // however, we're using a manual reset event for broadcasts which
00226     // stays set until we reset it, so we don't lose the broadcast.
00227     unlockMutex(mutex);
00228 
00229     // wait for a signal or broadcast
00230     DWORD result = WaitForMultipleObjects(3, handles, FALSE, winTimeout);
00231 
00232     // cancel takes priority
00233     if (result != WAIT_OBJECT_0 + 2 &&
00234         WaitForSingleObject(handles[2], 0) == WAIT_OBJECT_0) {
00235         result = WAIT_OBJECT_0 + 2;
00236     }
00237 
00238     // update the waiter count and check if we're the last waiter
00239     lockMutex(cond->m_waitCountMutex);
00240     --cond->m_waitCount;
00241     const bool last = (result == WAIT_OBJECT_0 + 1 && cond->m_waitCount == 0);
00242     unlockMutex(cond->m_waitCountMutex);
00243 
00244     // reset the broadcast event if we're the last waiter
00245     if (last) {
00246         ResetEvent(cond->m_events[CArchCondImpl::kBroadcast]);
00247     }
00248 
00249     // reacquire the mutex
00250     lockMutex(mutex);
00251 
00252     // cancel thread if necessary
00253     if (result == WAIT_OBJECT_0 + 2) {
00254         ARCH->testCancelThread();
00255     }
00256 
00257     // return success or failure
00258     return (result == WAIT_OBJECT_0 + 0 ||
00259             result == WAIT_OBJECT_0 + 1);
00260 }
00261 
00262 CArchMutex
00263 CArchMultithreadWindows::newMutex()
00264 {
00265     CArchMutexImpl* mutex = new CArchMutexImpl;
00266     InitializeCriticalSection(&mutex->m_mutex);
00267     return mutex;
00268 }
00269 
00270 void
00271 CArchMultithreadWindows::closeMutex(CArchMutex mutex)
00272 {
00273     DeleteCriticalSection(&mutex->m_mutex);
00274     delete mutex;
00275 }
00276 
00277 void
00278 CArchMultithreadWindows::lockMutex(CArchMutex mutex)
00279 {
00280     EnterCriticalSection(&mutex->m_mutex);
00281 }
00282 
00283 void
00284 CArchMultithreadWindows::unlockMutex(CArchMutex mutex)
00285 {
00286     LeaveCriticalSection(&mutex->m_mutex);
00287 }
00288 
00289 CArchThread
00290 CArchMultithreadWindows::newThread(ThreadFunc func, void* data)
00291 {
00292     lockMutex(m_threadMutex);
00293 
00294     // create thread impl for new thread
00295     CArchThreadImpl* thread = new CArchThreadImpl;
00296     thread->m_func          = func;
00297     thread->m_userData      = data;
00298 
00299     // create thread
00300     unsigned int id;
00301     thread->m_thread = reinterpret_cast<HANDLE>(_beginthreadex(NULL, 0,
00302                                 threadFunc, (void*)thread, 0, &id));
00303     thread->m_id     = static_cast<DWORD>(id);
00304 
00305     // check if thread was started
00306     if (thread->m_thread == 0) {
00307         // failed to start thread so clean up
00308         delete thread;
00309         thread = NULL;
00310     }
00311     else {
00312         // add thread to list
00313         insert(thread);
00314 
00315         // increment ref count to account for the thread itself
00316         refThread(thread);
00317     }
00318 
00319     // note that the child thread will wait until we release this mutex
00320     unlockMutex(m_threadMutex);
00321 
00322     return thread;
00323 }
00324 
00325 CArchThread
00326 CArchMultithreadWindows::newCurrentThread()
00327 {
00328     lockMutex(m_threadMutex);
00329     CArchThreadImpl* thread = find(GetCurrentThreadId());
00330     unlockMutex(m_threadMutex);
00331     assert(thread != NULL);
00332     return thread;
00333 }
00334 
00335 void
00336 CArchMultithreadWindows::closeThread(CArchThread thread)
00337 {
00338     assert(thread != NULL);
00339 
00340     // decrement ref count and clean up thread if no more references
00341     if (--thread->m_refCount == 0) {
00342         // close the handle (main thread has a NULL handle)
00343         if (thread->m_thread != NULL) {
00344             CloseHandle(thread->m_thread);
00345         }
00346 
00347         // remove thread from list
00348         lockMutex(m_threadMutex);
00349         assert(findNoRefOrCreate(thread->m_id) == thread);
00350         erase(thread);
00351         unlockMutex(m_threadMutex);
00352 
00353         // done with thread
00354         delete thread;
00355     }
00356 }
00357 
00358 CArchThread
00359 CArchMultithreadWindows::copyThread(CArchThread thread)
00360 {
00361     refThread(thread);
00362     return thread;
00363 }
00364 
00365 void
00366 CArchMultithreadWindows::cancelThread(CArchThread thread)
00367 {
00368     assert(thread != NULL);
00369 
00370     // set cancel flag
00371     SetEvent(thread->m_cancel);
00372 }
00373 
00374 void
00375 CArchMultithreadWindows::setPriorityOfThread(CArchThread thread, int n)
00376 {
00377     struct CPriorityInfo {
00378     public:
00379         DWORD       m_class;
00380         int         m_level;
00381     };
00382     static const CPriorityInfo s_pClass[] = {
00383         { IDLE_PRIORITY_CLASS,     THREAD_PRIORITY_IDLE         },
00384         { IDLE_PRIORITY_CLASS,     THREAD_PRIORITY_LOWEST       },
00385         { IDLE_PRIORITY_CLASS,     THREAD_PRIORITY_BELOW_NORMAL },
00386         { IDLE_PRIORITY_CLASS,     THREAD_PRIORITY_NORMAL       },
00387         { IDLE_PRIORITY_CLASS,     THREAD_PRIORITY_ABOVE_NORMAL },
00388         { IDLE_PRIORITY_CLASS,     THREAD_PRIORITY_HIGHEST      },
00389         { NORMAL_PRIORITY_CLASS,   THREAD_PRIORITY_LOWEST       },
00390         { NORMAL_PRIORITY_CLASS,   THREAD_PRIORITY_BELOW_NORMAL },
00391         { NORMAL_PRIORITY_CLASS,   THREAD_PRIORITY_NORMAL       },
00392         { NORMAL_PRIORITY_CLASS,   THREAD_PRIORITY_ABOVE_NORMAL },
00393         { NORMAL_PRIORITY_CLASS,   THREAD_PRIORITY_HIGHEST      },
00394         { HIGH_PRIORITY_CLASS,     THREAD_PRIORITY_LOWEST       },
00395         { HIGH_PRIORITY_CLASS,     THREAD_PRIORITY_BELOW_NORMAL },
00396         { HIGH_PRIORITY_CLASS,     THREAD_PRIORITY_NORMAL       },
00397         { HIGH_PRIORITY_CLASS,     THREAD_PRIORITY_ABOVE_NORMAL },
00398         { HIGH_PRIORITY_CLASS,     THREAD_PRIORITY_HIGHEST      },
00399         { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_IDLE         },
00400         { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_LOWEST       },
00401         { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_BELOW_NORMAL },
00402         { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_NORMAL       },
00403         { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_ABOVE_NORMAL },
00404         { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_HIGHEST      },
00405         { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_TIME_CRITICAL}
00406     };
00407 #if defined(_DEBUG)
00408     // don't use really high priorities when debugging
00409     static const size_t s_pMax  = 13;
00410 #else
00411     static const size_t s_pMax  = sizeof(s_pClass) / sizeof(s_pClass[0]) - 1;
00412 #endif
00413     static const size_t s_pBase = 8;    // index of normal priority
00414 
00415     assert(thread != NULL);
00416 
00417     size_t index;
00418     if (n > 0 && s_pBase < (size_t)n) {
00419         // lowest priority
00420         index = 0;
00421     }
00422     else {
00423         index = (size_t)((int)s_pBase - n);
00424         if (index > s_pMax) {
00425             // highest priority
00426             index = s_pMax;
00427         }
00428     }
00429     SetPriorityClass(GetCurrentProcess(), s_pClass[index].m_class);
00430     SetThreadPriority(thread->m_thread, s_pClass[index].m_level);
00431 }
00432 
00433 void
00434 CArchMultithreadWindows::testCancelThread()
00435 {
00436     // find current thread
00437     lockMutex(m_threadMutex);
00438     CArchThreadImpl* thread = findNoRef(GetCurrentThreadId());
00439     unlockMutex(m_threadMutex);
00440 
00441     // test cancel on thread
00442     testCancelThreadImpl(thread);
00443 }
00444 
00445 bool
00446 CArchMultithreadWindows::wait(CArchThread target, double timeout)
00447 {
00448     assert(target != NULL);
00449 
00450     lockMutex(m_threadMutex);
00451 
00452     // find current thread
00453     CArchThreadImpl* self = findNoRef(GetCurrentThreadId());
00454 
00455     // ignore wait if trying to wait on ourself
00456     if (target == self) {
00457         unlockMutex(m_threadMutex);
00458         return false;
00459     }
00460 
00461     // ref the target so it can't go away while we're watching it
00462     refThread(target);
00463 
00464     unlockMutex(m_threadMutex);
00465 
00466     // convert timeout
00467     DWORD t;
00468     if (timeout < 0.0) {
00469         t = INFINITE;
00470     }
00471     else {
00472         t = (DWORD)(1000.0 * timeout);
00473     }
00474 
00475     // wait for this thread to be cancelled or woken up or for the
00476     // target thread to terminate.
00477     HANDLE handles[2];
00478     handles[0] = target->m_exit;
00479     handles[1] = self->m_cancel;
00480     DWORD result = WaitForMultipleObjects(2, handles, FALSE, t);
00481 
00482     // cancel takes priority
00483     if (result != WAIT_OBJECT_0 + 1 &&
00484         WaitForSingleObject(handles[1], 0) == WAIT_OBJECT_0) {
00485         result = WAIT_OBJECT_0 + 1;
00486     }
00487 
00488     // release target
00489     closeThread(target);
00490 
00491     // handle result
00492     switch (result) {
00493     case WAIT_OBJECT_0 + 0:
00494         // target thread terminated
00495         return true;
00496 
00497     case WAIT_OBJECT_0 + 1:
00498         // this thread was cancelled.  does not return.
00499         testCancelThreadImpl(self);
00500 
00501     default:
00502         // timeout or error
00503         return false;
00504     }
00505 }
00506 
00507 bool
00508 CArchMultithreadWindows::isSameThread(CArchThread thread1, CArchThread thread2)
00509 {
00510     return (thread1 == thread2);
00511 }
00512 
00513 bool
00514 CArchMultithreadWindows::isExitedThread(CArchThread thread)
00515 {
00516     // poll exit event
00517     return (WaitForSingleObject(thread->m_exit, 0) == WAIT_OBJECT_0);
00518 }
00519 
00520 void*
00521 CArchMultithreadWindows::getResultOfThread(CArchThread thread)
00522 {
00523     lockMutex(m_threadMutex);
00524     void* result = thread->m_result;
00525     unlockMutex(m_threadMutex);
00526     return result;
00527 }
00528 
00529 IArchMultithread::ThreadID
00530 CArchMultithreadWindows::getIDOfThread(CArchThread thread)
00531 {
00532     return static_cast<ThreadID>(thread->m_id);
00533 }
00534 
00535 void
00536 CArchMultithreadWindows::setSignalHandler(
00537                 ESignal signal, SignalFunc func, void* userData)
00538 {
00539     lockMutex(m_threadMutex);
00540     m_signalFunc[signal]     = func;
00541     m_signalUserData[signal] = userData;
00542     unlockMutex(m_threadMutex);
00543 }
00544 
00545 void
00546 CArchMultithreadWindows::raiseSignal(ESignal signal)
00547 {
00548     lockMutex(m_threadMutex);
00549     if (m_signalFunc[signal] != NULL) {
00550         m_signalFunc[signal](signal, m_signalUserData[signal]);
00551         ARCH->unblockPollSocket(m_mainThread);
00552     }
00553     else if (signal == kINTERRUPT || signal == kTERMINATE) {
00554         ARCH->cancelThread(m_mainThread);
00555     }
00556     unlockMutex(m_threadMutex);
00557 }
00558 
00559 CArchThreadImpl*
00560 CArchMultithreadWindows::find(DWORD id)
00561 {
00562     CArchThreadImpl* impl = findNoRef(id);
00563     if (impl != NULL) {
00564         refThread(impl);
00565     }
00566     return impl;
00567 }
00568 
00569 CArchThreadImpl*
00570 CArchMultithreadWindows::findNoRef(DWORD id)
00571 {
00572     CArchThreadImpl* impl = findNoRefOrCreate(id);
00573     if (impl == NULL) {
00574         // create thread for calling thread which isn't in our list and
00575         // add it to the list.  this won't normally happen but it can if
00576         // the system calls us under a new thread, like it does when we
00577         // run as a service.
00578         impl           = new CArchThreadImpl;
00579         impl->m_thread = NULL;
00580         impl->m_id     = GetCurrentThreadId();
00581         insert(impl);
00582     }
00583     return impl;
00584 }
00585 
00586 CArchThreadImpl*
00587 CArchMultithreadWindows::findNoRefOrCreate(DWORD id)
00588 {
00589     // linear search
00590     for (CThreadList::const_iterator index  = m_threadList.begin();
00591                                      index != m_threadList.end(); ++index) {
00592         if ((*index)->m_id == id) {
00593             return *index;
00594         }
00595     }
00596     return NULL;
00597 }
00598 
00599 void
00600 CArchMultithreadWindows::insert(CArchThreadImpl* thread)
00601 {
00602     assert(thread != NULL);
00603 
00604     // thread shouldn't already be on the list
00605     assert(findNoRefOrCreate(thread->m_id) == NULL);
00606 
00607     // append to list
00608     m_threadList.push_back(thread);
00609 }
00610 
00611 void
00612 CArchMultithreadWindows::erase(CArchThreadImpl* thread)
00613 {
00614     for (CThreadList::iterator index  = m_threadList.begin();
00615                                index != m_threadList.end(); ++index) {
00616         if (*index == thread) {
00617             m_threadList.erase(index);
00618             break;
00619         }
00620     }
00621 }
00622 
00623 void
00624 CArchMultithreadWindows::refThread(CArchThreadImpl* thread)
00625 {
00626     assert(thread != NULL);
00627     assert(findNoRefOrCreate(thread->m_id) != NULL);
00628     ++thread->m_refCount;
00629 }
00630 
00631 void
00632 CArchMultithreadWindows::testCancelThreadImpl(CArchThreadImpl* thread)
00633 {
00634     assert(thread != NULL);
00635 
00636     // poll cancel event.  return if not set.
00637     const DWORD result = WaitForSingleObject(thread->m_cancel, 0);
00638     if (result != WAIT_OBJECT_0) {
00639         return;
00640     }
00641 
00642     // update cancel state
00643     lockMutex(m_threadMutex);
00644     bool cancel          = !thread->m_cancelling;
00645     thread->m_cancelling = true;
00646     ResetEvent(thread->m_cancel);
00647     unlockMutex(m_threadMutex);
00648 
00649     // unwind thread's stack if cancelling
00650     if (cancel) {
00651         throw XThreadCancel();
00652     }
00653 }
00654 
00655 unsigned int __stdcall
00656 CArchMultithreadWindows::threadFunc(void* vrep)
00657 {
00658     // get the thread
00659     CArchThreadImpl* thread = reinterpret_cast<CArchThreadImpl*>(vrep);
00660 
00661     // run thread
00662     s_instance->doThreadFunc(thread);
00663 
00664     // terminate the thread
00665     return 0;
00666 }
00667 
00668 void
00669 CArchMultithreadWindows::doThreadFunc(CArchThread thread)
00670 {
00671     // wait for parent to initialize this object
00672     lockMutex(m_threadMutex);
00673     unlockMutex(m_threadMutex);
00674 
00675     void* result = NULL;
00676     try {
00677         // go
00678         result = (*thread->m_func)(thread->m_userData);
00679     }
00680 
00681     catch (XThreadCancel&) {
00682         // client called cancel()
00683     }
00684     catch (...) {
00685         // note -- don't catch (...) to avoid masking bugs
00686         SetEvent(thread->m_exit);
00687         closeThread(thread);
00688         throw;
00689     }
00690 
00691     // thread has exited
00692     lockMutex(m_threadMutex);
00693     thread->m_result = result;
00694     unlockMutex(m_threadMutex);
00695     SetEvent(thread->m_exit);
00696 
00697     // done with thread
00698     closeThread(thread);
00699 }

Generated on Fri Nov 6 00:18:44 2009 for synergy-plus by  doxygen 1.4.7