00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041 #ifndef __CCXX_THREAD_H__
00042 #define __CCXX_THREAD_H__
00043
00044 #ifndef WIN32
00045 #define __CCXX_POSIX
00046
00047 #ifndef _REENTRANT
00048 #define _REENTRANT
00049 #endif
00050
00051 #ifndef _THREAD_SAFE
00052 #define _THREAD_SAFE
00053 #endif
00054 #else // WIN32
00055 #include <setjmp.h>
00056 #endif // !WIN32
00057
00058 #ifndef __CCXX_CONFIG_H__
00059 #include <cc++/config.h>
00060 #endif
00061
00062 #ifndef __CCXX_EXCEPTION_H__
00063 #include <cc++/exception.h>
00064 #endif
00065
00066 #ifndef WIN32
00067 #if defined(__FreeBSD__) && __FreeBSD__ <= 3
00068 #define _SYSV_SEMAPHORES
00069 #endif
00070
00071 #ifndef HAVE_PTHREAD_H
00072 #include <pthread.h>
00073 #ifndef _SYSV_SEMAPHORES
00074 #include <semaphore.h>
00075 #endif
00076 #endif
00077 #endif // !WIN32
00078
00079 #if _MSC_VER > 1000
00080 #pragma once
00081 #endif
00082
00083 #ifndef WIN32
00084 #include <time.h>
00085 #include <signal.h>
00086 #include <setjmp.h>
00087 #include <unistd.h>
00088
00089 #ifdef __linux__
00090 #define _SIG_THREAD_ALARM
00091 #endif
00092
00093 #ifdef _THR_UNIXWARE
00094 #undef PTHREAD_MUTEXTYPE_RECURSIVE
00095 #endif
00096
00097 typedef pthread_t cctid_t;
00098 typedef unsigned long timeout_t;
00099 #else // WIN32
00100 typedef DWORD cctid_t;
00101 typedef DWORD timeout_t;
00102
00103 #define MAX_SEM_VALUE 1000000
00104 #if defined(__MINGW32__) || defined(__CYGWIN32__)
00105 #include <Windows32/CommonFunctions.h>
00106 #else
00107 __declspec(dllimport) long __stdcall InterlockedIncrement(long *);
00108 __declspec(dllimport) long __stdcall InterlockedDecrement(long *);
00109 __declspec(dllimport) long __stdcall InterlockedExchange(long *, long);
00110 #endif
00111
00112 #endif // !WIN32
00113
00114 #ifdef __NAMESPACES__
00115 namespace ost {
00116 #endif
00117
00118 enum throw_t {
00119 THROW_NOTHING,
00120 THROW_OBJECT,
00121 THROW_EXCEPTION
00122 };
00123 typedef enum throw_t throw_t;
00124 class Thread;
00125
00126 #define TIMEOUT_INF ~((timeout_t) 0)
00127
00128 #define ENTER_CRITICAL EnterMutex();
00129 #define LEAVE_CRITICAL LeaveMutex();
00130 #define ENTER_DEFERRED setCancel(THREAD_CANCEL_DEFERRED);
00131 #define LEAVE_DEFERRED setCancel(THREAD_CANCEL_IMMEDIATE);
00132
00133 enum thread_cancel_t
00134 {
00135 THREAD_CANCEL_INITIAL=0,
00136 THREAD_CANCEL_DEFERRED=1,
00137 THREAD_CANCEL_IMMEDIATE,
00138 THREAD_CANCEL_DISABLED,
00139 THREAD_CANCEL_DEFAULT=THREAD_CANCEL_DEFERRED
00140 };
00141 typedef enum thread_cancel_t thread_cancel_t;
00142
00143 enum thread_suspend_t
00144 {
00145 THREAD_SUSPEND_ENABLE,
00146 THREAD_SUSPEND_DISABLE
00147 };
00148 typedef enum thread_suspend_t thread_suspend_t;
00149
00150 #ifndef WIN32
00151
00152
00153
00154
00155
00156
00157 #undef sleep
00158 #define psleep(x) (sleep)(x)
00159
00160 extern "C" void execHandler(Thread *th);
00161
00162 #endif // !WIN32
00163
00164
00165 CCXX_EXPORT(Thread*) getThread(void);
00166 CCXX_EXPORT(throw_t) getException(void);
00167 CCXX_EXPORT(void) setException(throw_t mode);
00168 CCXX_EXPORT(void) ccxx_sleep(timeout_t msec);
00169 CCXX_EXPORT(void) ccxx_yield(void);
00170
00171 #undef Yield
00172 #define sleep(x) ccxx_sleep((x) * 1000)
00173 #define yield() ccxx_yield()
00174
00175 #ifdef WIN32
00176 CCXX_EXPORT(DWORD) waitThread(HANDLE hRef, timeout_t timeout);
00177 #endif
00178
00179 class Conditional;
00180 class CCXX_CLASS_EXPORT Event;
00181
00225 class CCXX_CLASS_EXPORT Mutex
00226 {
00227 friend class Conditional;
00228 friend class Event;
00229 private:
00230 #ifndef WIN32
00231 #ifndef PTHREAD_MUTEXTYPE_RECURSIVE
00232 volatile int _level;
00233 volatile Thread *_tid;
00234 #endif
00235
00243 pthread_mutex_t _mutex;
00244 #else
00245 HANDLE mutex;
00246 #endif
00247
00248 public:
00252 Mutex();
00253
00259 virtual ~Mutex()
00260 #ifndef WIN32
00261 {pthread_mutex_destroy(&_mutex);};
00262 #else
00263 {::CloseHandle(mutex);};
00264 #endif
00265
00273 #ifndef WIN32
00274 #ifdef PTHREAD_MUTEXTYPE_RECURSIVE
00275 inline void EnterMutex(void)
00276 {pthread_mutex_lock(&_mutex);};
00277 #else
00278 void EnterMutex(void);
00279 #endif
00280 #else // WIN32
00281 void EnterMutex(void)
00282 {waitThread(mutex, INFINITE);};
00283 #endif // !WIN32
00284
00295 bool TryEnterMutex(void)
00296 #ifdef WIN32
00297 {return (waitThread(mutex, 0) == WAIT_OBJECT_0);}
00298 #endif
00299 ;
00300
00311 #ifndef WIN32
00312 #ifdef PTHREAD_MUTEXTYPE_RECURSIVE
00313 inline void LeaveMutex(void)
00314 {pthread_mutex_unlock(&_mutex);};
00315 #else
00316 void LeaveMutex(void);
00317 #endif
00318 #else
00319 void LeaveMutex(void);
00320 #endif
00321 };
00322
00344 class MutexLock
00345 {
00346 private:
00347 Mutex& mutex;
00348 public:
00352 MutexLock( Mutex& _mutex ) : mutex( _mutex )
00353 { mutex.EnterMutex(); }
00357
00358 ~MutexLock()
00359 { mutex.LeaveMutex(); }
00360 };
00361
00370 class CCXX_CLASS_EXPORT ThreadLock
00371 {
00372 private:
00373 #ifdef HAVE_PTHREAD_RWLOCK
00374 pthread_rwlock_t _lock;
00375 #else
00376 Mutex mutex;
00377 #endif
00378
00379 public:
00383 ThreadLock();
00384
00388 virtual ~ThreadLock();
00389
00393 void ReadLock(void);
00394
00398 void WriteLock(void);
00399
00405 bool TryReadLock(void);
00406
00412 bool TryWriteLock(void);
00413
00417 void Unlock(void);
00418 };
00419
00429 class CCXX_CLASS_EXPORT MutexCounter : public Mutex
00430 {
00431 private:
00432 int counter;
00433
00434 public:
00435 MutexCounter();
00436 MutexCounter(int initial);
00437
00438 friend CCXX_EXPORT(int) operator++(MutexCounter &mc);
00439 friend CCXX_EXPORT(int) operator--(MutexCounter &mc);
00440 };
00441
00452 class CCXX_CLASS_EXPORT AtomicCounter
00453 {
00454 #ifndef WIN32
00455 private:
00456 #ifdef HAVE_ATOMIC
00457 atomic_t atomic;
00458 #else
00459 int counter;
00460 Mutex lock;
00461 #endif
00462
00463 public:
00467 AtomicCounter();
00468
00474 AtomicCounter(int value);
00475
00476 int operator++(void);
00477 int operator--(void);
00478 int operator+=(int change);
00479 int operator-=(int change);
00480 int operator+(int change);
00481 int operator-(int change);
00482 int operator=(int value);
00483 bool operator!(void);
00484 operator int();
00485 #else
00486 private:
00487 long atomic;
00488
00489 public:
00490 inline AtomicCounter()
00491 {atomic = 0;};
00492
00493 inline AtomicCounter(int value)
00494 {atomic = value;};
00495
00496 inline int operator++(void)
00497 {return InterlockedIncrement(&atomic);};
00498
00499 inline int operator--(void)
00500 {return InterlockedDecrement(&atomic);};
00501
00502 int operator+=(int change);
00503
00504 int operator-=(int change);
00505
00506 inline int operator+(int change)
00507 {return atomic + change;};
00508
00509 inline int operator-(int change)
00510 {return atomic - change;};
00511
00512 inline int operator=(int value)
00513 {return InterlockedExchange(&atomic, value);};
00514
00515 inline bool operator!(void)
00516 {return (atomic == 0) ? true : false;};
00517
00518 inline operator int()
00519 {return atomic;};
00520 #endif
00521 };
00522
00523
00524 #ifndef WIN32
00525
00535 class Conditional : public Mutex
00536 {
00537 private:
00538 pthread_cond_t _cond;
00539
00540 public:
00544 Conditional();
00545
00549 virtual ~Conditional()
00550 {pthread_cond_destroy(&_cond);};
00551
00557 void Signal(bool broadcast);
00558
00562 void Wait(timeout_t timer = 0);
00563 };
00564 #endif
00565
00583 class CCXX_CLASS_EXPORT Semaphore
00584 {
00585 private:
00586 #ifndef WIN32
00587 #ifdef _SYSV_SEMAPHORES
00588 int _semaphore;
00589 #else
00590 sem_t _semaphore;
00591 #endif
00592 #else // WIN32
00593 HANDLE semObject;
00594 #endif // !WIN32
00595
00596 public:
00605 Semaphore(size_t resource = 0)
00606 #ifdef WIN32
00607
00608 {semObject = CreateSemaphore((LPSECURITY_ATTRIBUTES)NULL, resource, MAX_SEM_VALUE, (LPCTSTR)NULL);}
00609 #endif
00610 ;
00611
00618 virtual ~Semaphore()
00619 #ifdef WIN32
00620 {CloseHandle(semObject);}
00621 #endif
00622 ;
00623
00637 void Wait(void)
00638 #ifdef WIN32
00639 {waitThread(semObject, INFINITE);}
00640 #endif
00641 ;
00642
00654 bool TryWait(void)
00655 #ifdef WIN32
00656 {return waitThread(semObject, 0) == WAIT_OBJECT_0;}
00657 #endif
00658 ;
00659
00671 void Post(void)
00672 #ifdef WIN32
00673 {ReleaseSemaphore(semObject, 1, (LPLONG)NULL);}
00674 #endif
00675 ;
00676
00677
00683 #ifndef WIN32
00684 int getValue(void);
00685 #endif
00686 };
00687
00701 class CCXX_CLASS_EXPORT Event
00702 #ifndef WIN32
00703 : private Mutex
00704 #endif
00705 {
00706 private:
00707 #ifndef WIN32
00708 pthread_cond_t _cond;
00709 bool _signaled;
00710 int _count;
00711 #else
00712 HANDLE cond;
00713 #endif
00714
00715 public:
00716 Event();
00717
00718 virtual ~Event()
00719 #ifndef WIN32
00720 {pthread_cond_destroy(&_cond);};
00721 #else
00722 {CloseHandle(cond);};
00723 #endif
00724
00731 void Reset(void)
00732 #ifndef WIN32
00733 {_signaled = false;};
00734 #else
00735 {ResetEvent(cond);};
00736 #endif
00737
00741 void Signal(void)
00742 #ifdef WIN32
00743 {SetEvent(cond);}
00744 #endif
00745 ;
00746
00755 bool Wait(timeout_t timer)
00756 #ifdef WIN32
00757 {return (waitThread(cond, timer) == WAIT_OBJECT_0);}
00758 #endif
00759 ;
00760
00761 bool Wait()
00762 #ifndef WIN32
00763 {return Wait(0); }
00764 #else
00765 {return (waitThread(cond, INFINITE) == WAIT_OBJECT_0);}
00766 #endif
00767 ;
00768 };
00769
00791 class CCXX_CLASS_EXPORT Buffer
00792 {
00793 private:
00794 Mutex lock_head, lock_tail;
00795 Semaphore size_head, size_tail;
00796 size_t _size;
00797 size_t _used;
00798
00799 protected:
00805 virtual int OnPeek(void *buf) = 0;
00811 virtual int OnWait(void *buf) = 0;
00817 virtual int OnPost(void *buf) = 0;
00818
00819 public:
00824 Buffer(size_t capacity);
00829 virtual ~Buffer()
00830 {return;};
00831
00836 inline size_t getSize(void)
00837 {return _size;};
00838
00845 inline size_t getUsed(void)
00846 {return _used;};
00847
00856 int Wait(void *buf);
00857
00865 int Post(void *buf);
00866
00873 int Peek(void *buf);
00874
00879 virtual bool isValid(void)
00880 {return true;};
00881 };
00882
00890 class CCXX_CLASS_EXPORT FixedBuffer : public Buffer
00891 {
00892 private:
00893 char *buf, *head, *tail;
00894 size_t objsize;
00895
00896 protected:
00902 int OnPeek(void *buf);
00903
00909 int OnWait(void *buf);
00910
00916 int OnPost(void *buf);
00917
00918 public:
00926 FixedBuffer(size_t capacity, size_t objsize);
00927
00934 FixedBuffer(const FixedBuffer &fb);
00935
00939 virtual ~FixedBuffer();
00940
00941 FixedBuffer &operator=(const FixedBuffer &fb);
00942
00943 bool isValid(void);
00944 };
00945
01093 class Thread
01094 {
01095 #ifndef WIN32
01096 friend class PosixThread;
01097 #endif
01098 private:
01099 friend class Slog;
01100
01101 static Thread *_main;
01102
01103 Thread *_parent;
01104 #ifndef WIN32
01105 pthread_t _tid;
01106 pthread_attr_t _attr;
01107 AtomicCounter _suspendcount;
01108 #else
01109 DWORD _tid;
01110 HANDLE _cancellation;
01111 #endif
01112 thread_cancel_t _cancel;
01113 jmp_buf _env;
01114 Semaphore *_start;
01115 int _msgpos;
01116 char _msgbuf[128];
01117 throw_t _throw;
01118
01119 #ifndef WIN32
01120 friend void execHandler(Thread *th);
01121 friend Thread *getThread(void);
01122 #else
01123 bool _active:1;
01124 bool _suspendEnable:1;
01125 static unsigned __stdcall Execute(Thread *th);
01126 HANDLE _hThread;
01127 #endif
01128
01129 protected:
01139 virtual void Run(void) = 0;
01140
01153 CCXX_MEMBER_EXPORT(virtual void) Final(void)
01154 {return;};
01155
01166 CCXX_MEMBER_EXPORT(virtual void) Initial(void)
01167 {return;};
01168
01178 CCXX_MEMBER_EXPORT(virtual void*) getExtended(void)
01179 {return NULL;};
01180
01188 CCXX_MEMBER_EXPORT(virtual void) Notify(Thread *th)
01189 {return;};
01190
01200 CCXX_MEMBER_EXPORT(void) Sleep(timeout_t msec)
01201 #ifndef WIN32
01202 {ccxx_sleep(msec);}
01203 #endif
01204 ;
01205
01211 inline void Exit(void)
01212 {longjmp(_env, 1);};
01213
01218 CCXX_MEMBER_EXPORT(void) Yield(void);
01219
01223 CCXX_MEMBER_EXPORT(void) testCancel(void);
01224
01233 CCXX_MEMBER_EXPORT(void) setCancel(thread_cancel_t mode);
01234
01242 CCXX_MEMBER_EXPORT(void) setSuspend(thread_suspend_t mode);
01243
01252 CCXX_MEMBER_EXPORT(void) Terminate(void);
01253
01257 inline void clrParent(void)
01258 {_parent = NULL;};
01259
01260 #ifdef WIN32
01261
01262 CCXX_MEMBER_EXPORT(DWORD) WaitHandle(HANDLE obj, timeout_t timeout);
01263 #endif
01264
01265 public:
01274 CCXX_MEMBER_EXPORT(CCXX_EMPTY) Thread(bool isMain);
01275
01288 CCXX_MEMBER_EXPORT(CCXX_EMPTY) Thread(int pri = 0, size_t stack = 0);
01289
01290
01291 #ifndef WIN32
01292
01299 Thread(const Thread &th);
01300 #endif
01301
01308 CCXX_MEMBER_EXPORT(virtual) ~Thread()
01309 {Terminate();};
01310
01323 CCXX_MEMBER_EXPORT(int) Start(Semaphore *start = 0);
01324
01333 CCXX_MEMBER_EXPORT(int) Detach(Semaphore *start = 0);
01334
01341 inline Thread *getParent(void)
01342 {return _parent;};
01343
01350 CCXX_MEMBER_EXPORT(void) Suspend(void);
01351
01355 CCXX_MEMBER_EXPORT(void) Resume(void);
01356
01363 inline thread_cancel_t getCancel(void)
01364 {return _cancel;};
01365
01372 bool isRunning(void)
01373 #ifdef WIN32
01374 {return (_tid != 0) ? true : false;}
01375 #endif
01376 ;
01377
01384 bool isThread(void)
01385 #ifdef WIN32
01386 {return ((_tid == GetCurrentThreadId())) ? true : false;}
01387 #endif
01388 ;
01389
01395 friend CCXX_EXPORT(throw_t) getException(void);
01396
01402 friend CCXX_EXPORT(void) setException(throw_t mode);
01403
01407 friend inline int start(Thread &th, Semaphore *start = 0)
01408 {return th.Start(start);};
01409
01416 friend inline void operator++(Thread &th)
01417 {th._start->Post();};
01418
01419 friend inline void operator--(Thread &th)
01420 {th._start->Wait();};
01421
01427 friend CCXX_EXPORT(void) ccxx_sleep(timeout_t msec);
01428
01429 #ifdef WIN32
01430
01431 inline bool isCancelled(void)
01432 {return waitThread(_cancellation, 0) == WAIT_OBJECT_0; };
01433
01434 inline bool isCancelled(timeout_t timer)
01435 {return waitThread(_cancellation, timer) == WAIT_OBJECT_0; };
01436
01437 friend CCXX_EXPORT(DWORD) waitThread(HANDLE hRef, timeout_t timeout);
01438 friend CCXX_EXPORT(void) ccxx_yield(void);
01439 #endif
01440 };
01441
01447 inline void suspend(Thread &th)
01448 { th.Suspend(); }
01449
01455 inline void resume(Thread &th)
01456 { th.Resume(); }
01457
01458 #ifndef WIN32
01459 extern "C" void sigHandler(int signo);
01460
01461 typedef int signo_t;
01462
01463 class PosixThread: public Thread
01464 {
01465 private:
01466 #ifndef _SIG_THREAD_ALARM
01467 static Thread *_timer;
01468 static Mutex _arm;
01469 #endif
01470
01471 time_t _alarm;
01472 friend void sigHandler(int signo);
01473 inline static void SignalThread(Thread* th,signo_t signo)
01474 {pthread_kill(th->_tid, signo);};
01475 protected:
01476
01483 inline void SignalParent(signo_t signo)
01484 { SignalThread(_parent,signo); };
01485
01492 inline void SignalMain(signo_t signo)
01493 { SignalThread(_main,signo);};
01494
01499 virtual void OnTimer(void)
01500 {return;};
01501
01506 virtual void OnHangup(void)
01507 {return;};
01508
01513 virtual void OnException(void)
01514 {return;};
01515
01520 virtual void OnDisconnect(void)
01521 {return;};
01522
01527 virtual void OnPolling(void)
01528 {return;};
01529
01536 virtual void OnSignal(int signo)
01537 {return;};
01538
01548 void setTimer(timeout_t timer);
01549
01556 timeout_t getTimer(void);
01557
01563 void endTimer(void);
01564
01571 void WaitSignal(signo_t signo);
01572
01579 void setSignal(int signo, bool mode);
01580 public:
01581
01587 inline void SignalThread(int signo)
01588 {SignalThread(this, signo);};
01589
01596 friend void siginstall(int signo);
01597 };
01598 inline void signal(PosixThread &th, int signo)
01599 {th.SignalThread(signo);};
01600 #endif
01601
01616 class CCXX_CLASS_EXPORT ThreadKey
01617 {
01618 private:
01619 #ifndef WIN32
01620 pthread_key_t key;
01621 #else
01622 DWORD key;
01623 #endif
01624
01625 public:
01629 ThreadKey();
01633 virtual ~ThreadKey();
01641 void *getKey(void);
01649 void setKey(void *);
01650 };
01651
01662 class CCXX_CLASS_EXPORT TimerPort
01663 {
01664 #ifndef WIN32
01665 struct timeval timer;
01666 #else
01667 DWORD timer;
01668 #endif
01669 bool active;
01670
01671 public:
01678 TimerPort();
01679
01688 void setTimer(timeout_t timeout = 0);
01689
01699 void incTimer(timeout_t timeout);
01700
01706 void endTimer(void);
01707
01718 timeout_t getTimer(void);
01719
01728 timeout_t getElapsed(void);
01729 };
01730
01731
01732
01733
01734 #undef signal
01735
01736 inline void *getKey(ThreadKey &tk)
01737 {return tk.getKey();};
01738
01739 inline void setKey(ThreadKey &tk, void *ptr)
01740 {tk.setKey(ptr);};
01741
01742 inline void operator++(Mutex &m)
01743 {m.EnterMutex();};
01744
01745 inline void operator--(Mutex &m)
01746 {m.LeaveMutex();};
01747
01748 inline void operator++(Semaphore &s)
01749 {s.Post();};
01750
01751 inline void operator--(Semaphore &s)
01752 {s.Wait();};
01753
01754 inline void operator ++(Event &s)
01755 {s.Signal();};
01756
01757 inline void operator --(Event &s)
01758 {s.Wait();};
01759
01760 inline void signal(Event &ev)
01761 {ev.Signal();};
01762
01763 inline void wait(Event &ev)
01764 {ev.Wait();};
01765
01766 inline void wait(Event &ev, timeout_t timer)
01767 {ev.Wait(timer);};
01768
01769 inline void reset(Event &ev)
01770 {ev.Reset();};
01771
01772 inline void signal(Semaphore &sem)
01773 {sem.Post();};
01774
01775 inline void wait(Semaphore &sem)
01776 {sem.Wait();};
01777
01778 inline int get(Buffer &b, void *o)
01779 {return b.Wait(o);};
01780
01781 inline int put(Buffer &b, void *o)
01782 {return b.Post(o);};
01783
01784 inline int peek(Buffer &b, void *o)
01785 {return b.Peek(o);};
01786
01787
01788
01789 #ifndef WIN32
01790
01791 struct timespec *gettimeout(struct timespec *spec, timeout_t timeout);
01792 void wait(signo_t signo);
01801 void pdetach(void);
01802 #endif // !WIN32
01803
01804
01805 #ifndef WIN32
01806 #if defined(HAVE_POLL_H) || defined(HAVE_SYS_POLL_H)
01807 #if defined(HAVE_SYS_STREAM_H)
01808 #if defined(__linux__)
01809 #define __CCXX_USE_POLL 1
01810 #endif
01811 #else
01812 #define __CCXX_USE_POLL 1
01813 #endif
01814 #endif
01815
01816 #ifdef __CCXX_USE_POLL
01817
01825 class Poller
01826 {
01827 private:
01828 int nufds;
01829 pollfd *ufds;
01830
01831 public:
01832 Poller();
01833
01834 virtual ~Poller();
01835
01843 pollfd *getList(int cnt);
01844
01850 inline pollfd *getList(void)
01851 {return ufds;};
01852 };
01853 #endif
01854 #endif // !WIN32
01855
01856 #ifdef COMMON_STD_EXCEPTION
01857
01863 class ThrException : public Exception
01864 {
01865 public:
01866 ThrException(const std::string &what_arg) : Exception(what_arg) {};
01867 };
01868
01875 class SyncException : public ThrException
01876 {
01877 public:
01878 SyncException(const std::string &what_arg) : ThrException(what_arg) {};
01879 };
01880 #endif
01881
01882 #ifdef __NAMESPACES__
01883 };
01884 #endif
01885
01886 #endif
01887