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