Win32(core): _endthreadex(0) instead of _endthread()
[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         int canc = vlc_savecancel ();
195         assert (p_mutex != &super_mutex); /* this one cannot be static */
196
197         vlc_mutex_lock (&super_mutex);
198         while (p_mutex->locked)
199         {
200             p_mutex->contention++;
201             vlc_cond_wait (&super_variable, &super_mutex);
202             p_mutex->contention--;
203         }
204         p_mutex->locked = true;
205         vlc_mutex_unlock (&super_mutex);
206         vlc_restorecancel (canc);
207         return;
208     }
209
210     EnterCriticalSection (&p_mutex->mutex);
211 }
212
213 int vlc_mutex_trylock (vlc_mutex_t *p_mutex)
214 {
215     if (!p_mutex->dynamic)
216     {   /* static mutexes */
217         int ret = EBUSY;
218
219         assert (p_mutex != &super_mutex); /* this one cannot be static */
220         vlc_mutex_lock (&super_mutex);
221         if (!p_mutex->locked)
222         {
223             p_mutex->locked = true;
224             ret = 0;
225         }
226         vlc_mutex_unlock (&super_mutex);
227         return ret;
228     }
229
230     return TryEnterCriticalSection (&p_mutex->mutex) ? 0 : EBUSY;
231 }
232
233 void vlc_mutex_unlock (vlc_mutex_t *p_mutex)
234 {
235     if (!p_mutex->dynamic)
236     {   /* static mutexes */
237         assert (p_mutex != &super_mutex); /* this one cannot be static */
238
239         vlc_mutex_lock (&super_mutex);
240         assert (p_mutex->locked);
241         p_mutex->locked = false;
242         if (p_mutex->contention)
243             vlc_cond_broadcast (&super_variable);
244         vlc_mutex_unlock (&super_mutex);
245         return;
246     }
247
248     LeaveCriticalSection (&p_mutex->mutex);
249 }
250
251 /*** Condition variables ***/
252 enum
253 {
254     CLOCK_MONOTONIC,
255     CLOCK_REALTIME,
256 };
257
258 static void vlc_cond_init_common (vlc_cond_t *p_condvar, unsigned clock)
259 {
260     /* Create a manual-reset event (manual reset is needed for broadcast). */
261     p_condvar->handle = CreateEvent (NULL, TRUE, FALSE, NULL);
262     if (!p_condvar->handle)
263         abort();
264     p_condvar->clock = clock;
265 }
266
267 void vlc_cond_init (vlc_cond_t *p_condvar)
268 {
269     vlc_cond_init_common (p_condvar, CLOCK_MONOTONIC);
270 }
271
272 void vlc_cond_init_daytime (vlc_cond_t *p_condvar)
273 {
274     vlc_cond_init_common (p_condvar, CLOCK_REALTIME);
275 }
276
277 void vlc_cond_destroy (vlc_cond_t *p_condvar)
278 {
279     CloseHandle (p_condvar->handle);
280 }
281
282 void vlc_cond_signal (vlc_cond_t *p_condvar)
283 {
284     /* NOTE: This will cause a broadcast, that is wrong.
285      * This will also wake up the next waiting thread if no threads are yet
286      * waiting, which is also wrong. However both of these issues are allowed
287      * by the provision for spurious wakeups. Better have too many wakeups
288      * than too few (= deadlocks). */
289     SetEvent (p_condvar->handle);
290 }
291
292 void vlc_cond_broadcast (vlc_cond_t *p_condvar)
293 {
294     SetEvent (p_condvar->handle);
295 }
296
297 void vlc_cond_wait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex)
298 {
299     DWORD result;
300
301     assert (p_mutex->dynamic); /* TODO */
302     do
303     {
304         vlc_testcancel ();
305         LeaveCriticalSection (&p_mutex->mutex);
306         result = WaitForSingleObjectEx (p_condvar->handle, INFINITE, TRUE);
307         EnterCriticalSection (&p_mutex->mutex);
308     }
309     while (result == WAIT_IO_COMPLETION);
310
311     assert (result != WAIT_ABANDONED); /* another thread failed to cleanup! */
312     assert (result != WAIT_FAILED);
313     ResetEvent (p_condvar->handle);
314 }
315
316 int vlc_cond_timedwait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex,
317                         mtime_t deadline)
318 {
319     DWORD result;
320
321     assert (p_mutex->dynamic); /* TODO */
322     do
323     {
324         vlc_testcancel ();
325
326         mtime_t total;
327         switch (p_condvar->clock)
328         {
329             case CLOCK_MONOTONIC:
330                 total = mdate();
331                 break;
332             case CLOCK_REALTIME: /* FIXME? sub-second precision */
333                 total = CLOCK_FREQ * time (NULL);
334                 break;
335             default:
336                 assert (0);
337         }
338         total = (deadline - total) / 1000;
339         if( total < 0 )
340             total = 0;
341
342         DWORD delay = (total > 0x7fffffff) ? 0x7fffffff : total;
343         LeaveCriticalSection (&p_mutex->mutex);
344         result = WaitForSingleObjectEx (p_condvar->handle, delay, TRUE);
345         EnterCriticalSection (&p_mutex->mutex);
346     }
347     while (result == WAIT_IO_COMPLETION);
348
349     assert (result != WAIT_ABANDONED);
350     assert (result != WAIT_FAILED);
351     ResetEvent (p_condvar->handle);
352
353     return (result == WAIT_OBJECT_0) ? 0 : ETIMEDOUT;
354 }
355
356 /*** Semaphore ***/
357 void vlc_sem_init (vlc_sem_t *sem, unsigned value)
358 {
359     *sem = CreateSemaphore (NULL, value, 0x7fffffff, NULL);
360     if (*sem == NULL)
361         abort ();
362 }
363
364 void vlc_sem_destroy (vlc_sem_t *sem)
365 {
366     CloseHandle (*sem);
367 }
368
369 int vlc_sem_post (vlc_sem_t *sem)
370 {
371     ReleaseSemaphore (*sem, 1, NULL);
372     return 0; /* FIXME */
373 }
374
375 void vlc_sem_wait (vlc_sem_t *sem)
376 {
377     DWORD result;
378
379     do
380     {
381         vlc_testcancel ();
382         result = WaitForSingleObjectEx (*sem, INFINITE, TRUE);
383     }
384     while (result == WAIT_IO_COMPLETION);
385 }
386
387 /*** Read/write locks */
388 /* SRW (Slim Read Write) locks are available in Vista+ only */
389 void vlc_rwlock_init (vlc_rwlock_t *lock)
390 {
391     vlc_mutex_init (&lock->mutex);
392     vlc_cond_init (&lock->read_wait);
393     vlc_cond_init (&lock->write_wait);
394     lock->readers = 0; /* active readers */
395     lock->writers = 0; /* waiting or active writers */
396     lock->writer = 0; /* ID of active writer */
397 }
398
399 /**
400  * Destroys an initialized unused read/write lock.
401  */
402 void vlc_rwlock_destroy (vlc_rwlock_t *lock)
403 {
404     vlc_cond_destroy (&lock->read_wait);
405     vlc_cond_destroy (&lock->write_wait);
406     vlc_mutex_destroy (&lock->mutex);
407 }
408
409 /**
410  * Acquires a read/write lock for reading. Recursion is allowed.
411  */
412 void vlc_rwlock_rdlock (vlc_rwlock_t *lock)
413 {
414     vlc_mutex_lock (&lock->mutex);
415     while (lock->writer != 0)
416         vlc_cond_wait (&lock->read_wait, &lock->mutex);
417     if (lock->readers == ULONG_MAX)
418         abort ();
419     lock->readers++;
420     vlc_mutex_unlock (&lock->mutex);
421 }
422
423 /**
424  * Acquires a read/write lock for writing. Recursion is not allowed.
425  */
426 void vlc_rwlock_wrlock (vlc_rwlock_t *lock)
427 {
428     vlc_mutex_lock (&lock->mutex);
429     if (lock->writers == ULONG_MAX)
430         abort ();
431     lock->writers++;
432     while ((lock->readers > 0) || (lock->writer != 0))
433         vlc_cond_wait (&lock->write_wait, &lock->mutex);
434     lock->writers--;
435     lock->writer = GetCurrentThreadId ();
436     vlc_mutex_unlock (&lock->mutex);
437 }
438
439 /**
440  * Releases a read/write lock.
441  */
442 void vlc_rwlock_unlock (vlc_rwlock_t *lock)
443 {
444     vlc_mutex_lock (&lock->mutex);
445     if (lock->readers > 0)
446         lock->readers--; /* Read unlock */
447     else
448         lock->writer = 0; /* Write unlock */
449
450     if (lock->writers > 0)
451     {
452         if (lock->readers == 0)
453             vlc_cond_signal (&lock->write_wait);
454     }
455     else
456         vlc_cond_broadcast (&lock->read_wait);
457     vlc_mutex_unlock (&lock->mutex);
458 }
459
460 /*** Thread-specific variables (TLS) ***/
461 int vlc_threadvar_create (vlc_threadvar_t *p_tls, void (*destr) (void *))
462 {
463 #warning FIXME: use destr() callback and stop leaking!
464     VLC_UNUSED( destr );
465
466     *p_tls = TlsAlloc();
467     return (*p_tls == TLS_OUT_OF_INDEXES) ? EAGAIN : 0;
468 }
469
470 void vlc_threadvar_delete (vlc_threadvar_t *p_tls)
471 {
472     TlsFree (*p_tls);
473 }
474
475 /**
476  * Sets a thread-local variable.
477  * @param key thread-local variable key (created with vlc_threadvar_create())
478  * @param value new value for the variable for the calling thread
479  * @return 0 on success, a system error code otherwise.
480  */
481 int vlc_threadvar_set (vlc_threadvar_t key, void *value)
482 {
483     return TlsSetValue (key, value) ? ENOMEM : 0;
484 }
485
486 /**
487  * Gets the value of a thread-local variable for the calling thread.
488  * This function cannot fail.
489  * @return the value associated with the given variable for the calling
490  * or NULL if there is no value.
491  */
492 void *vlc_threadvar_get (vlc_threadvar_t key)
493 {
494     return TlsGetValue (key);
495 }
496
497
498 /*** Threads ***/
499 void vlc_threads_setup (libvlc_int_t *p_libvlc)
500 {
501     (void) p_libvlc;
502 }
503
504 struct vlc_entry_data
505 {
506     void * (*func) (void *);
507     void *  data;
508     vlc_sem_t ready;
509 #ifdef UNDER_CE
510     HANDLE  cancel_event;
511 #endif
512 };
513
514 static unsigned __stdcall vlc_entry (void *p)
515 {
516     struct vlc_entry_data *entry = p;
517     vlc_cancel_t cancel_data = VLC_CANCEL_INIT;
518     void *(*func) (void *) = entry->func;
519     void *data = entry->data;
520
521 #ifdef UNDER_CE
522     cancel_data.cancel_event = entry->cancel_event;
523 #endif
524     vlc_threadvar_set (cancel_key, &cancel_data);
525     vlc_sem_post (&entry->ready);
526     func (data);
527     return 0;
528 }
529
530 int vlc_clone (vlc_thread_t *p_handle, void * (*entry) (void *), void *data,
531                int priority)
532 {
533     int err = ENOMEM;
534     HANDLE hThread;
535
536     struct vlc_entry_data *entry_data = malloc (sizeof (*entry_data));
537     if (entry_data == NULL)
538         return ENOMEM;
539     entry_data->func = entry;
540     entry_data->data = data;
541     vlc_sem_init (&entry_data->ready, 0);
542
543 #ifndef UNDER_CE
544     /* When using the MSVCRT C library you have to use the _beginthreadex
545      * function instead of CreateThread, otherwise you'll end up with
546      * memory leaks and the signal functions not working (see Microsoft
547      * Knowledge Base, article 104641) */
548     hThread = (HANDLE)(uintptr_t)
549         _beginthreadex (NULL, 0, vlc_entry, entry_data, CREATE_SUSPENDED, NULL);
550     if (! hThread)
551     {
552         err = errno;
553         goto error;
554     }
555
556     /* Thread closes the handle when exiting, duplicate it here
557      * to be on the safe side when joining. */
558     if (!DuplicateHandle (GetCurrentProcess (), hThread,
559                           GetCurrentProcess (), p_handle, 0, FALSE,
560                           DUPLICATE_SAME_ACCESS))
561     {
562         CloseHandle (hThread);
563         goto error;
564     }
565
566 #else
567     vlc_thread_t th = malloc (sizeof (*th));
568     if (th == NULL)
569         goto error;
570     th->cancel_event = CreateEvent (NULL, FALSE, FALSE, NULL);
571     if (th->cancel_event == NULL)
572     {
573         free (th);
574         goto error;
575     }
576     entry_data->cancel_event = th->cancel_event;
577
578     /* Not sure if CREATE_SUSPENDED + ResumeThread() is any useful on WinCE.
579      * Thread handles act up, too. */
580     th->handle = CreateThread (NULL, 128*1024, vlc_entry, entry_data,
581                                CREATE_SUSPENDED, NULL);
582     if (th->handle == NULL)
583     {
584         CloseHandle (th->cancel_event);
585         free (th);
586         goto error;
587     }
588
589     *p_handle = th;
590     hThread = th->handle;
591
592 #endif
593
594     ResumeThread (hThread);
595     if (priority)
596         SetThreadPriority (hThread, priority);
597
598     /* Prevent cancellation until cancel_data is initialized. */
599     /* XXX: This could be postponed to vlc_cancel() or avoided completely by
600      * passing the "right" pointer to vlc_cancel_self(). */
601     vlc_sem_wait (&entry_data->ready);
602     vlc_sem_destroy (&entry_data->ready);
603     free (entry_data);
604
605     return 0;
606
607 error:
608     vlc_sem_destroy (&entry_data->ready);
609     free (entry_data);
610     return err;
611 }
612
613 void vlc_join (vlc_thread_t handle, void **result)
614 {
615 #ifdef UNDER_CE
616 # define handle handle->handle
617 #endif
618     do
619         vlc_testcancel ();
620     while (WaitForSingleObjectEx (handle, INFINITE, TRUE)
621                                                         == WAIT_IO_COMPLETION);
622
623     CloseHandle (handle);
624     assert (result == NULL); /* <- FIXME if ever needed */
625 #ifdef UNDER_CE
626 # undef handle
627     CloseHandle (handle->cancel_event);
628     free (handle);
629 #endif
630 }
631
632 void vlc_detach (vlc_thread_t handle)
633 {
634 #ifndef UNDER_CE
635     CloseHandle (handle);
636 #else
637     /* FIXME: handle->cancel_event leak */
638     CloseHandle (handle->handle);
639     free (handle);
640 #endif
641 }
642
643 /*** Thread cancellation ***/
644
645 /* APC procedure for thread cancellation */
646 static void CALLBACK vlc_cancel_self (ULONG_PTR dummy)
647 {
648     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
649
650     if (likely(nfo != NULL))
651         nfo->killed = true;
652
653     (void)dummy;
654 }
655
656 void vlc_cancel (vlc_thread_t thread_id)
657 {
658 #ifndef UNDER_CE
659     QueueUserAPC (vlc_cancel_self, thread_id, 0);
660 #else
661     SetEvent (thread_id->cancel_event);
662 #endif
663 }
664
665 int vlc_savecancel (void)
666 {
667     int state;
668
669     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
670     if (nfo == NULL)
671         return false; /* Main thread - cannot be cancelled anyway */
672
673     state = nfo->killable;
674     nfo->killable = false;
675     return state;
676 }
677
678 void vlc_restorecancel (int state)
679 {
680     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
681     assert (state == false || state == true);
682
683     if (nfo == NULL)
684         return; /* Main thread - cannot be cancelled anyway */
685
686     assert (!nfo->killable);
687     nfo->killable = state != 0;
688 }
689
690 void vlc_testcancel (void)
691 {
692     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
693     if (nfo == NULL)
694         return; /* Main thread - cannot be cancelled anyway */
695
696     if (nfo->killable && nfo->killed)
697     {
698         for (vlc_cleanup_t *p = nfo->cleaners; p != NULL; p = p->next)
699              p->proc (p->data);
700 #ifndef UNDER_CE
701         _endthreadex(0);
702 #else
703         ExitThread(0);
704 #endif
705     }
706 }
707
708 void vlc_control_cancel (int cmd, ...)
709 {
710     /* NOTE: This function only modifies thread-specific data, so there is no
711      * need to lock anything. */
712     va_list ap;
713
714     vlc_cancel_t *nfo = vlc_threadvar_get (cancel_key);
715     if (nfo == NULL)
716         return; /* Main thread - cannot be cancelled anyway */
717
718     va_start (ap, cmd);
719     switch (cmd)
720     {
721         case VLC_CLEANUP_PUSH:
722         {
723             /* cleaner is a pointer to the caller stack, no need to allocate
724              * and copy anything. As a nice side effect, this cannot fail. */
725             vlc_cleanup_t *cleaner = va_arg (ap, vlc_cleanup_t *);
726             cleaner->next = nfo->cleaners;
727             nfo->cleaners = cleaner;
728             break;
729         }
730
731         case VLC_CLEANUP_POP:
732         {
733             nfo->cleaners = nfo->cleaners->next;
734             break;
735         }
736     }
737     va_end (ap);
738 }
739
740
741 /*** Timers ***/
742 struct vlc_timer
743 {
744 #ifndef UNDER_CE
745     HANDLE handle;
746 #else
747     unsigned id;
748     unsigned interval;
749 #endif
750     void (*func) (void *);
751     void *data;
752 };
753
754 #ifndef UNDER_CE
755 static void CALLBACK vlc_timer_do (void *val, BOOLEAN timeout)
756 {
757     struct vlc_timer *timer = val;
758
759     assert (timeout);
760     timer->func (timer->data);
761 }
762 #else
763 static void CALLBACK vlc_timer_do (unsigned timer_id, unsigned msg,
764                                    DWORD_PTR user, DWORD_PTR unused1,
765                                    DWORD_PTR unused2)
766 {
767     struct vlc_timer *timer = (struct vlc_timer *) user;
768     assert (timer_id == timer->id);
769     (void) msg;
770     (void) unused1;
771     (void) unused2;
772
773     timer->func (timer->data);
774
775     if (timer->interval)
776     {
777         mtime_t interval = timer->interval * 1000;
778         vlc_timer_schedule (timer, false, interval, interval);
779     }
780 }
781 #endif
782
783 int vlc_timer_create (vlc_timer_t *id, void (*func) (void *), void *data)
784 {
785     struct vlc_timer *timer = malloc (sizeof (*timer));
786
787     if (timer == NULL)
788         return ENOMEM;
789     timer->func = func;
790     timer->data = data;
791 #ifndef UNDER_CE
792     timer->handle = INVALID_HANDLE_VALUE;
793 #else
794     timer->id = 0;
795     timer->interval = 0;
796 #endif
797     *id = timer;
798     return 0;
799 }
800
801 void vlc_timer_destroy (vlc_timer_t timer)
802 {
803 #ifndef UNDER_CE
804     if (timer->handle != INVALID_HANDLE_VALUE)
805         DeleteTimerQueueTimer (NULL, timer->handle, INVALID_HANDLE_VALUE);
806 #else
807     if (timer->id)
808         timeKillEvent (timer->id);
809     /* FIXME: timers that have not yet completed will trigger use-after-free */
810 #endif
811     free (timer);
812 }
813
814 void vlc_timer_schedule (vlc_timer_t timer, bool absolute,
815                          mtime_t value, mtime_t interval)
816 {
817 #ifndef UNDER_CE
818     if (timer->handle != INVALID_HANDLE_VALUE)
819     {
820         DeleteTimerQueueTimer (NULL, timer->handle, NULL);
821         timer->handle = INVALID_HANDLE_VALUE;
822     }
823 #else
824     if (timer->id)
825     {
826         timeKillEvent (timer->id);
827         timer->id = 0;
828         timer->interval = 0;
829     }
830 #endif
831     if (value == 0)
832         return; /* Disarm */
833
834     if (absolute)
835         value -= mdate ();
836     value = (value + 999) / 1000;
837     interval = (interval + 999) / 1000;
838
839 #ifndef UNDER_CE
840     if (!CreateTimerQueueTimer (&timer->handle, NULL, vlc_timer_do, timer,
841                                 value, interval, WT_EXECUTEDEFAULT))
842 #else
843     TIMECAPS caps;
844     timeGetDevCaps (&caps, sizeof(caps));
845
846     unsigned delay = value;
847     delay = __MAX(delay, caps.wPeriodMin);
848     delay = __MIN(delay, caps.wPeriodMax);
849
850     unsigned event = TIME_ONESHOT;
851
852     if (interval == delay)
853         event = TIME_PERIODIC;
854     else if (interval)
855         timer->interval = interval;
856
857     timer->id = timeSetEvent (delay, delay / 20, vlc_timer_do, (DWORD) timer,
858                               event);
859     if (!timer->id)
860 #endif
861         abort ();
862 }
863
864 unsigned vlc_timer_getoverrun (vlc_timer_t timer)
865 {
866     (void)timer;
867     return 0;
868 }