Move core Win32 files to their own directory
[vlc.git] / src / win32 / thread.c
1 /*****************************************************************************
2  * thread.c : Win32 back-end for LibVLC
3  *****************************************************************************
4  * Copyright (C) 1999-2009 the VideoLAN team
5  *
6  * Authors: Jean-Marc Dressler <polux@via.ecp.fr>
7  *          Samuel Hocevar <sam@zoy.org>
8  *          Gildas Bazin <gbazin@netcourrier.com>
9  *          Clément Sténac
10  *          Rémi Denis-Courmont
11  *          Pierre Ynard
12  *
13  * This program is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2 of the License, or
16  * (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software
25  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
26  *****************************************************************************/
27
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31
32 #include <vlc_common.h>
33
34 #include "libvlc.h"
35 #include <stdarg.h>
36 #include <assert.h>
37 #include <limits.h>
38 #include <errno.h>
39 #ifdef UNDER_CE
40 # include <mmsystem.h>
41 #endif
42
43 static vlc_threadvar_t cancel_key;
44
45 /**
46  * Per-thread cancellation data
47  */
48 typedef struct vlc_cancel_t
49 {
50     vlc_cleanup_t *cleaners;
51 #ifdef UNDER_CE
52     HANDLE         cancel_event;
53 #endif
54     bool           killable;
55     bool           killed;
56 } vlc_cancel_t;
57
58 #ifndef UNDER_CE
59 # define VLC_CANCEL_INIT { NULL, true, false }
60 #else
61 # define VLC_CANCEL_INIT { NULL, NULL, true, false }
62 #endif
63
64 #ifdef UNDER_CE
65 static void CALLBACK vlc_cancel_self (ULONG_PTR dummy);
66
67 static DWORD vlc_cancelable_wait (DWORD count, const HANDLE *handles,
68                                   DWORD delay)
69 {
70     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
71     if (nfo == NULL)
72     {
73         /* Main thread - cannot be cancelled anyway */
74         return WaitForMultipleObjects (count, handles, FALSE, delay);
75     }
76     HANDLE new_handles[count + 1];
77     memcpy(new_handles, handles, count * sizeof(HANDLE));
78     new_handles[count] = nfo->cancel_event;
79     DWORD result = WaitForMultipleObjects (count + 1, new_handles, FALSE,
80                                            delay);
81     if (result == WAIT_OBJECT_0 + count)
82     {
83         vlc_cancel_self (NULL);
84         return WAIT_IO_COMPLETION;
85     }
86     else
87     {
88         return result;
89     }
90 }
91
92 DWORD SleepEx (DWORD dwMilliseconds, BOOL bAlertable)
93 {
94     if (bAlertable)
95     {
96         DWORD result = vlc_cancelable_wait (0, NULL, dwMilliseconds);
97         return (result == WAIT_TIMEOUT) ? 0 : WAIT_IO_COMPLETION;
98     }
99     else
100     {
101         Sleep(dwMilliseconds);
102         return 0;
103     }
104 }
105
106 DWORD WaitForSingleObjectEx (HANDLE hHandle, DWORD dwMilliseconds,
107                              BOOL bAlertable)
108 {
109     if (bAlertable)
110     {
111         /* The MSDN documentation specifies different return codes,
112          * but in practice they are the same. We just check that it
113          * remains so. */
114 #if WAIT_ABANDONED != WAIT_ABANDONED_0
115 # error Windows headers changed, code needs to be rewritten!
116 #endif
117         return vlc_cancelable_wait (1, &hHandle, dwMilliseconds);
118     }
119     else
120     {
121         return WaitForSingleObject (hHandle, dwMilliseconds);
122     }
123 }
124
125 DWORD WaitForMultipleObjectsEx (DWORD nCount, const HANDLE *lpHandles,
126                                 BOOL bWaitAll, DWORD dwMilliseconds,
127                                 BOOL bAlertable)
128 {
129     if (bAlertable)
130     {
131         /* We do not support the bWaitAll case */
132         assert (! bWaitAll);
133         return vlc_cancelable_wait (nCount, lpHandles, dwMilliseconds);
134     }
135     else
136     {
137         return WaitForMultipleObjects (nCount, lpHandles, bWaitAll,
138                                        dwMilliseconds);
139     }
140 }
141 #endif
142
143 vlc_mutex_t super_mutex;
144 vlc_cond_t  super_variable;
145
146 BOOL WINAPI DllMain (HINSTANCE hinstDll, DWORD fdwReason, LPVOID lpvReserved)
147 {
148     (void) hinstDll;
149     (void) lpvReserved;
150
151     switch (fdwReason)
152     {
153         case DLL_PROCESS_ATTACH:
154             vlc_mutex_init (&super_mutex);
155             vlc_cond_init (&super_variable);
156             vlc_threadvar_create (&cancel_key, free);
157             break;
158
159         case DLL_PROCESS_DETACH:
160             vlc_threadvar_delete( &cancel_key );
161             vlc_cond_destroy (&super_variable);
162             vlc_mutex_destroy (&super_mutex);
163             break;
164     }
165     return TRUE;
166 }
167
168 /*** Mutexes ***/
169 void vlc_mutex_init( vlc_mutex_t *p_mutex )
170 {
171     /* This creates a recursive mutex. This is OK as fast mutexes have
172      * no defined behavior in case of recursive locking. */
173     InitializeCriticalSection (&p_mutex->mutex);
174     p_mutex->dynamic = true;
175 }
176
177 void vlc_mutex_init_recursive( vlc_mutex_t *p_mutex )
178 {
179     InitializeCriticalSection( &p_mutex->mutex );
180     p_mutex->dynamic = true;
181 }
182
183
184 void vlc_mutex_destroy (vlc_mutex_t *p_mutex)
185 {
186     assert (p_mutex->dynamic);
187     DeleteCriticalSection (&p_mutex->mutex);
188 }
189
190 void vlc_mutex_lock (vlc_mutex_t *p_mutex)
191 {
192     if (!p_mutex->dynamic)
193     {   /* static mutexes */
194         assert (p_mutex != &super_mutex); /* this one cannot be static */
195
196         vlc_mutex_lock (&super_mutex);
197         while (p_mutex->locked)
198         {
199             p_mutex->contention++;
200             vlc_cond_wait (&super_variable, &super_mutex);
201             p_mutex->contention--;
202         }
203         p_mutex->locked = true;
204         vlc_mutex_unlock (&super_mutex);
205         return;
206     }
207
208     EnterCriticalSection (&p_mutex->mutex);
209 }
210
211 int vlc_mutex_trylock (vlc_mutex_t *p_mutex)
212 {
213     if (!p_mutex->dynamic)
214     {   /* static mutexes */
215         int ret = EBUSY;
216
217         assert (p_mutex != &super_mutex); /* this one cannot be static */
218         vlc_mutex_lock (&super_mutex);
219         if (!p_mutex->locked)
220         {
221             p_mutex->locked = true;
222             ret = 0;
223         }
224         vlc_mutex_unlock (&super_mutex);
225         return ret;
226     }
227
228     return TryEnterCriticalSection (&p_mutex->mutex) ? 0 : EBUSY;
229 }
230
231 void vlc_mutex_unlock (vlc_mutex_t *p_mutex)
232 {
233     if (!p_mutex->dynamic)
234     {   /* static mutexes */
235         assert (p_mutex != &super_mutex); /* this one cannot be static */
236
237         vlc_mutex_lock (&super_mutex);
238         assert (p_mutex->locked);
239         p_mutex->locked = false;
240         if (p_mutex->contention)
241             vlc_cond_broadcast (&super_variable);
242         vlc_mutex_unlock (&super_mutex);
243         return;
244     }
245
246     LeaveCriticalSection (&p_mutex->mutex);
247 }
248
249 /*** Condition variables ***/
250 void vlc_cond_init( vlc_cond_t *p_condvar )
251 {
252     /* Create a manual-reset event (manual reset is needed for broadcast). */
253     *p_condvar = CreateEvent (NULL, TRUE, FALSE, NULL);
254     if (!*p_condvar)
255         abort();
256 }
257
258 void vlc_cond_destroy (vlc_cond_t *p_condvar)
259 {
260     CloseHandle (*p_condvar);
261 }
262
263 void vlc_cond_signal (vlc_cond_t *p_condvar)
264 {
265     /* NOTE: This will cause a broadcast, that is wrong.
266      * This will also wake up the next waiting thread if no threads are yet
267      * waiting, which is also wrong. However both of these issues are allowed
268      * by the provision for spurious wakeups. Better have too many wakeups
269      * than too few (= deadlocks). */
270     SetEvent (*p_condvar);
271 }
272
273 void vlc_cond_broadcast (vlc_cond_t *p_condvar)
274 {
275     SetEvent (*p_condvar);
276 }
277
278 void vlc_cond_wait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex)
279 {
280     DWORD result;
281
282     assert (p_mutex->dynamic); /* TODO */
283     do
284     {
285         vlc_testcancel ();
286         LeaveCriticalSection (&p_mutex->mutex);
287         result = WaitForSingleObjectEx (*p_condvar, INFINITE, TRUE);
288         EnterCriticalSection (&p_mutex->mutex);
289     }
290     while (result == WAIT_IO_COMPLETION);
291
292     assert (result != WAIT_ABANDONED); /* another thread failed to cleanup! */
293     assert (result != WAIT_FAILED);
294     ResetEvent (*p_condvar);
295 }
296
297 int vlc_cond_timedwait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex,
298                         mtime_t deadline)
299 {
300     DWORD result;
301
302     assert (p_mutex->dynamic); /* TODO */
303     do
304     {
305         vlc_testcancel ();
306
307         mtime_t total = (deadline - mdate ())/1000;
308         if( total < 0 )
309             total = 0;
310
311         DWORD delay = (total > 0x7fffffff) ? 0x7fffffff : total;
312         LeaveCriticalSection (&p_mutex->mutex);
313         result = WaitForSingleObjectEx (*p_condvar, delay, TRUE);
314         EnterCriticalSection (&p_mutex->mutex);
315     }
316     while (result == WAIT_IO_COMPLETION);
317
318     assert (result != WAIT_ABANDONED);
319     assert (result != WAIT_FAILED);
320     ResetEvent (*p_condvar);
321
322     return (result == WAIT_OBJECT_0) ? 0 : ETIMEDOUT;
323 }
324
325 /*** Semaphore ***/
326 void vlc_sem_init (vlc_sem_t *sem, unsigned value)
327 {
328     *sem = CreateSemaphore (NULL, value, 0x7fffffff, NULL);
329     if (*sem == NULL)
330         abort ();
331 }
332
333 void vlc_sem_destroy (vlc_sem_t *sem)
334 {
335     CloseHandle (*sem);
336 }
337
338 int vlc_sem_post (vlc_sem_t *sem)
339 {
340     ReleaseSemaphore (*sem, 1, NULL);
341     return 0; /* FIXME */
342 }
343
344 void vlc_sem_wait (vlc_sem_t *sem)
345 {
346     DWORD result;
347
348     do
349     {
350         vlc_testcancel ();
351         result = WaitForSingleObjectEx (*sem, INFINITE, TRUE);
352     }
353     while (result == WAIT_IO_COMPLETION);
354 }
355
356 /*** Read/write locks */
357 /* SRW (Slim Read Write) locks are available in Vista+ only */
358 void vlc_rwlock_init (vlc_rwlock_t *lock)
359 {
360     vlc_mutex_init (&lock->mutex);
361     vlc_cond_init (&lock->read_wait);
362     vlc_cond_init (&lock->write_wait);
363     lock->readers = 0; /* active readers */
364     lock->writers = 0; /* waiting or active writers */
365     lock->writer = 0; /* ID of active writer */
366 }
367
368 /**
369  * Destroys an initialized unused read/write lock.
370  */
371 void vlc_rwlock_destroy (vlc_rwlock_t *lock)
372 {
373     vlc_cond_destroy (&lock->read_wait);
374     vlc_cond_destroy (&lock->write_wait);
375     vlc_mutex_destroy (&lock->mutex);
376 }
377
378 /**
379  * Acquires a read/write lock for reading. Recursion is allowed.
380  */
381 void vlc_rwlock_rdlock (vlc_rwlock_t *lock)
382 {
383     vlc_mutex_lock (&lock->mutex);
384     while (lock->writer != 0)
385         vlc_cond_wait (&lock->read_wait, &lock->mutex);
386     if (lock->readers == ULONG_MAX)
387         abort ();
388     lock->readers++;
389     vlc_mutex_unlock (&lock->mutex);
390 }
391
392 /**
393  * Acquires a read/write lock for writing. Recursion is not allowed.
394  */
395 void vlc_rwlock_wrlock (vlc_rwlock_t *lock)
396 {
397     vlc_mutex_lock (&lock->mutex);
398     if (lock->writers == ULONG_MAX)
399         abort ();
400     lock->writers++;
401     while ((lock->readers > 0) || (lock->writer != 0))
402         vlc_cond_wait (&lock->write_wait, &lock->mutex);
403     lock->writers--;
404     lock->writer = GetCurrentThreadId ();
405     vlc_mutex_unlock (&lock->mutex);
406 }
407
408 /**
409  * Releases a read/write lock.
410  */
411 void vlc_rwlock_unlock (vlc_rwlock_t *lock)
412 {
413     vlc_mutex_lock (&lock->mutex);
414     if (lock->readers > 0)
415         lock->readers--; /* Read unlock */
416     else
417         lock->writer = 0; /* Write unlock */
418
419     if (lock->writers > 0)
420     {
421         if (lock->readers == 0)
422             vlc_cond_signal (&lock->write_wait);
423     }
424     else
425         vlc_cond_broadcast (&lock->read_wait);
426     vlc_mutex_unlock (&lock->mutex);
427 }
428
429 /*** Thread-specific variables (TLS) ***/
430 int vlc_threadvar_create (vlc_threadvar_t *p_tls, void (*destr) (void *))
431 {
432 #warning FIXME: use destr() callback and stop leaking!
433     VLC_UNUSED( destr );
434
435     *p_tls = TlsAlloc();
436     return (*p_tls == TLS_OUT_OF_INDEXES) ? EAGAIN : 0;
437 }
438
439 void vlc_threadvar_delete (vlc_threadvar_t *p_tls)
440 {
441     TlsFree (*p_tls);
442 }
443
444 /**
445  * Sets a thread-local variable.
446  * @param key thread-local variable key (created with vlc_threadvar_create())
447  * @param value new value for the variable for the calling thread
448  * @return 0 on success, a system error code otherwise.
449  */
450 int vlc_threadvar_set (vlc_threadvar_t key, void *value)
451 {
452     return TlsSetValue (key, value) ? ENOMEM : 0;
453 }
454
455 /**
456  * Gets the value of a thread-local variable for the calling thread.
457  * This function cannot fail.
458  * @return the value associated with the given variable for the calling
459  * or NULL if there is no value.
460  */
461 void *vlc_threadvar_get (vlc_threadvar_t key)
462 {
463     return TlsGetValue (key);
464 }
465
466
467 /*** Threads ***/
468 void vlc_threads_setup (libvlc_int_t *p_libvlc)
469 {
470     (void) p_libvlc;
471 }
472
473 struct vlc_entry_data
474 {
475     void * (*func) (void *);
476     void *  data;
477 #ifdef UNDER_CE
478     HANDLE  cancel_event;
479 #endif
480 };
481
482 static unsigned __stdcall vlc_entry (void *p)
483 {
484     vlc_cancel_t cancel_data = VLC_CANCEL_INIT;
485     struct vlc_entry_data data;
486
487     memcpy (&data, p, sizeof (data));
488     free (p);
489
490 #ifdef UNDER_CE
491     cancel_data.cancel_event = data.cancel_event;
492 #endif
493
494     vlc_threadvar_set (cancel_key, &cancel_data);
495     data.func (data.data);
496     return 0;
497 }
498
499 int vlc_clone (vlc_thread_t *p_handle, void * (*entry) (void *), void *data,
500                int priority)
501 {
502     int err = ENOMEM;
503     HANDLE hThread;
504
505     struct vlc_entry_data *entry_data = malloc (sizeof (*entry_data));
506     if (entry_data == NULL)
507         return ENOMEM;
508     entry_data->func = entry;
509     entry_data->data = data;
510
511 #ifndef UNDER_CE
512     /* When using the MSVCRT C library you have to use the _beginthreadex
513      * function instead of CreateThread, otherwise you'll end up with
514      * memory leaks and the signal functions not working (see Microsoft
515      * Knowledge Base, article 104641) */
516     hThread = (HANDLE)(uintptr_t)
517         _beginthreadex (NULL, 0, vlc_entry, entry_data, CREATE_SUSPENDED, NULL);
518     if (! hThread)
519     {
520         err = errno;
521         goto error;
522     }
523
524     /* Thread closes the handle when exiting, duplicate it here
525      * to be on the safe side when joining. */
526     if (!DuplicateHandle (GetCurrentProcess (), hThread,
527                           GetCurrentProcess (), p_handle, 0, FALSE,
528                           DUPLICATE_SAME_ACCESS))
529     {
530         CloseHandle (hThread);
531         goto error;
532     }
533
534 #else
535     vlc_thread_t th = malloc (sizeof (*th));
536     if (th == NULL)
537         goto error;
538     th->cancel_event = CreateEvent (NULL, FALSE, FALSE, NULL);
539     if (th->cancel_event == NULL)
540     {
541         free (th);
542         goto error;
543     }
544     entry_data->cancel_event = th->cancel_event;
545
546     /* Not sure if CREATE_SUSPENDED + ResumeThread() is any useful on WinCE.
547      * Thread handles act up, too. */
548     th->handle = CreateThread (NULL, 128*1024, vlc_entry, entry_data,
549                                CREATE_SUSPENDED, NULL);
550     if (th->handle == NULL)
551     {
552         CloseHandle (th->cancel_event);
553         free (th);
554         goto error;
555     }
556
557     *p_handle = th;
558     hThread = th->handle;
559
560 #endif
561
562     ResumeThread (hThread);
563     if (priority)
564         SetThreadPriority (hThread, priority);
565
566     return 0;
567
568 error:
569     free (entry_data);
570     return err;
571 }
572
573 void vlc_join (vlc_thread_t handle, void **result)
574 {
575 #ifdef UNDER_CE
576 # define handle handle->handle
577 #endif
578     do
579         vlc_testcancel ();
580     while (WaitForSingleObjectEx (handle, INFINITE, TRUE)
581                                                         == WAIT_IO_COMPLETION);
582
583     CloseHandle (handle);
584     assert (result == NULL); /* <- FIXME if ever needed */
585 #ifdef UNDER_CE
586 # undef handle
587     CloseHandle (handle->cancel_event);
588     free (handle);
589 #endif
590 }
591
592 void vlc_detach (vlc_thread_t handle)
593 {
594 #ifndef UNDER_CE
595     CloseHandle (handle);
596 #else
597     /* FIXME: handle->cancel_event leak */
598     CloseHandle (handle->handle);
599     free (handle);
600 #endif
601 }
602
603 /*** Thread cancellation ***/
604
605 /* APC procedure for thread cancellation */
606 static void CALLBACK vlc_cancel_self (ULONG_PTR dummy)
607 {
608     (void)dummy;
609     vlc_control_cancel (VLC_DO_CANCEL);
610 }
611
612 void vlc_cancel (vlc_thread_t thread_id)
613 {
614 #ifndef UNDER_CE
615     QueueUserAPC (vlc_cancel_self, thread_id, 0);
616 #else
617     SetEvent (thread_id->cancel_event);
618 #endif
619 }
620
621 int vlc_savecancel (void)
622 {
623     int state;
624
625     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
626     if (nfo == NULL)
627         return false; /* Main thread - cannot be cancelled anyway */
628
629     state = nfo->killable;
630     nfo->killable = false;
631     return state;
632 }
633
634 void vlc_restorecancel (int state)
635 {
636     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
637     assert (state == false || state == true);
638
639     if (nfo == NULL)
640         return; /* Main thread - cannot be cancelled anyway */
641
642     assert (!nfo->killable);
643     nfo->killable = state != 0;
644 }
645
646 void vlc_testcancel (void)
647 {
648     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
649     if (nfo == NULL)
650         return; /* Main thread - cannot be cancelled anyway */
651
652     if (nfo->killable && nfo->killed)
653     {
654         for (vlc_cleanup_t *p = nfo->cleaners; p != NULL; p = p->next)
655              p->proc (p->data);
656 #ifndef UNDER_CE
657         _endthread ();
658 #else
659         ExitThread(0);
660 #endif
661     }
662 }
663
664 void vlc_control_cancel (int cmd, ...)
665 {
666     /* NOTE: This function only modifies thread-specific data, so there is no
667      * need to lock anything. */
668     va_list ap;
669
670     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
671     if (nfo == NULL)
672         return; /* Main thread - cannot be cancelled anyway */
673
674     va_start (ap, cmd);
675     switch (cmd)
676     {
677         case VLC_DO_CANCEL:
678             nfo->killed = true;
679             break;
680
681         case VLC_CLEANUP_PUSH:
682         {
683             /* cleaner is a pointer to the caller stack, no need to allocate
684              * and copy anything. As a nice side effect, this cannot fail. */
685             vlc_cleanup_t *cleaner = va_arg (ap, vlc_cleanup_t *);
686             cleaner->next = nfo->cleaners;
687             nfo->cleaners = cleaner;
688             break;
689         }
690
691         case VLC_CLEANUP_POP:
692         {
693             nfo->cleaners = nfo->cleaners->next;
694             break;
695         }
696     }
697     va_end (ap);
698 }
699
700
701 /*** Timers ***/
702 struct vlc_timer
703 {
704 #ifndef UNDER_CE
705     HANDLE handle;
706 #else
707     unsigned id;
708     unsigned interval;
709 #endif
710     void (*func) (void *);
711     void *data;
712 };
713
714 #ifndef UNDER_CE
715 static void CALLBACK vlc_timer_do (void *val, BOOLEAN timeout)
716 {
717     struct vlc_timer *timer = val;
718
719     assert (timeout);
720     timer->func (timer->data);
721 }
722 #else
723 static void CALLBACK vlc_timer_do (unsigned timer_id, unsigned msg,
724                                    DWORD_PTR user, DWORD_PTR unused1,
725                                    DWORD_PTR unused2)
726 {
727     struct vlc_timer *timer = (struct vlc_timer *) user;
728     assert (timer_id == timer->id);
729     (void) msg;
730     (void) unused1;
731     (void) unused2;
732
733     timer->func (timer->data);
734
735     if (timer->interval)
736     {
737         mtime_t interval = timer->interval * 1000;
738         vlc_timer_schedule (timer, false, interval, interval);
739     }
740 }
741 #endif
742
743 int vlc_timer_create (vlc_timer_t *id, void (*func) (void *), void *data)
744 {
745     struct vlc_timer *timer = malloc (sizeof (*timer));
746
747     if (timer == NULL)
748         return ENOMEM;
749     timer->func = func;
750     timer->data = data;
751 #ifndef UNDER_CE
752     timer->handle = INVALID_HANDLE_VALUE;
753 #else
754     timer->id = 0;
755     timer->interval = 0;
756 #endif
757     *id = timer;
758     return 0;
759 }
760
761 void vlc_timer_destroy (vlc_timer_t timer)
762 {
763 #ifndef UNDER_CE
764     if (timer->handle != INVALID_HANDLE_VALUE)
765         DeleteTimerQueueTimer (NULL, timer->handle, INVALID_HANDLE_VALUE);
766 #else
767     if (timer->id)
768         timeKillEvent (timer->id);
769     /* FIXME: timers that have not yet completed will trigger use-after-free */
770 #endif
771     free (timer);
772 }
773
774 void vlc_timer_schedule (vlc_timer_t timer, bool absolute,
775                          mtime_t value, mtime_t interval)
776 {
777 #ifndef UNDER_CE
778     if (timer->handle != INVALID_HANDLE_VALUE)
779     {
780         DeleteTimerQueueTimer (NULL, timer->handle, NULL);
781         timer->handle = INVALID_HANDLE_VALUE;
782     }
783 #else
784     if (timer->id)
785     {
786         timeKillEvent (timer->id);
787         timer->id = 0;
788         timer->interval = 0;
789     }
790 #endif
791     if (value == 0)
792         return; /* Disarm */
793
794     if (absolute)
795         value -= mdate ();
796     value = (value + 999) / 1000;
797     interval = (interval + 999) / 1000;
798
799 #ifndef UNDER_CE
800     if (!CreateTimerQueueTimer (&timer->handle, NULL, vlc_timer_do, timer,
801                                 value, interval, WT_EXECUTEDEFAULT))
802 #else
803     TIMECAPS caps;
804     timeGetDevCaps (&caps, sizeof(caps));
805
806     unsigned delay = value;
807     delay = __MAX(delay, caps.wPeriodMin);
808     delay = __MIN(delay, caps.wPeriodMax);
809
810     unsigned event = TIME_ONESHOT;
811
812     if (interval == delay)
813         event = TIME_PERIODIC;
814     else if (interval)
815         timer->interval = interval;
816
817     timer->id = timeSetEvent (delay, delay / 20, vlc_timer_do, (DWORD) timer,
818                               event);
819     if (!timer->id)
820 #endif
821         abort ();
822 }
823
824 unsigned vlc_timer_getoverrun (vlc_timer_t timer)
825 {
826     (void)timer;
827     return 0;
828 }