The events are then fired after the callback completes.
Signed-off-by: Liam Murphy liampm32@gmail.com --- v3: Use block comments instead of line comments --- dlls/winmm/tests/wave.c | 1 - dlls/winmm/waveform.c | 165 ++++++++++++++++++++++++++++++---------- 2 files changed, 124 insertions(+), 42 deletions(-)
diff --git a/dlls/winmm/tests/wave.c b/dlls/winmm/tests/wave.c index 619c4f9339e..61c8cae72a5 100644 --- a/dlls/winmm/tests/wave.c +++ b/dlls/winmm/tests/wave.c @@ -881,7 +881,6 @@ static void CALLBACK reentrancy_callback_func(HWAVEOUT hwo, UINT uMsg,
data->call_num += 1;
- todo_wine_if(data->call_num == 3) ok(data->running_thread != GetCurrentThreadId(), "winmm callback called reentrantly, with message %u\n", uMsg); if (data->running_thread) { if (data->running_thread != GetCurrentThreadId()) trace("Callback running on two threads simultaneously\n"); diff --git a/dlls/winmm/waveform.c b/dlls/winmm/waveform.c index 1159b48b483..32104d5abbb 100644 --- a/dlls/winmm/waveform.c +++ b/dlls/winmm/waveform.c @@ -67,12 +67,20 @@ WINE_DEFAULT_DEBUG_CHANNEL(winmm); #define AC_BUFLEN (10 * 100000) #define MAX_DEVICES 256 #define MAPPER_INDEX 0x3F +#define CB_RUNNING 0x10
typedef struct _WINMM_CBInfo { DWORD_PTR callback; DWORD_PTR user; + /* Layout: 0b00000000_000r0ttt + * where t: callback type (`DCB_*` constants) + * r: Whether the callback is currently running, to avoid reentrancy. + * The 0 in the middle is `DCB_NOSWITCH`. */ DWORD flags; HWAVE hwave; + /* Used to buffer data from `WIM_DATA` or `WOM_DONE` if the callback is still running, + * depending on whether this is an input or output device respectively. */ + WAVEHDR *first, *last; } WINMM_CBInfo;
struct _WINMM_MMDevice; @@ -169,6 +177,12 @@ typedef struct _WINMM_OpenInfo { WAVEFORMATEX *format; DWORD_PTR callback; DWORD_PTR cb_user; + /* The high half of the bits are the same as `WINMM_CBInfo.flags`, + * and the low half have the layout 0b00000000_0000dmaq, + * where d: `WAVE_FORMAT_DIRECT`, + * m: `WAVE_MAPPED`, + * a: `WAVE_ALLOWSYNC`, + * q: `WAVE_FORMAT_QUERY`. */ DWORD flags; BOOL reset; } WINMM_OpenInfo; @@ -370,13 +384,103 @@ static WINMM_Device *WINMM_GetDeviceFromHWAVE(HWAVE hwave) return device; }
+/* Note: the lock on `device` must already have been acquired before calling this function. */ +static inline void WINMM_SendBufferedMessages(WINMM_Device *device) { + WINMM_CBInfo* info = &device->cb_info; + /* Make a copy of this to pass to the callback while within the critical section, + * since it can be mutated once we leave the critical section. */ + DWORD cb_type = info->flags; + + while (info->first) { + WAVEHDR* hdr; + WORD new_msg; + + if (device->render) { + new_msg = WOM_DONE; + } else { + new_msg = WIM_DATA; + } + + hdr = info->first; + info->first = hdr->lpNext; + hdr->lpNext = NULL; + + LeaveCriticalSection(&device->lock); + + DriverCallback(info->callback, cb_type, (HDRVR)info->hwave, + new_msg, info->user, (DWORD_PTR)hdr, 0); + + EnterCriticalSection(&device->lock); + } +} + /* Note: NotifyClient should never be called while holding the device lock - * since the client may call wave* functions from within the callback. */ -static inline void WINMM_NotifyClient(WINMM_CBInfo *info, WORD msg, DWORD_PTR param1, - DWORD_PTR param2) + * since the client may call wave* functions from within the callback. + * + * Any `WAVEHDR`s passed to this function must no longer be referenced by any other `WAVEHDR`s, + * so that their linked list can be reused to buffer them when this is called from within the callback. */ +static inline void WINMM_NotifyClient(WINMM_Device *device, WORD msg, WAVEHDR* param1, + WAVEHDR* param2) { - DriverCallback(info->callback, info->flags, (HDRVR)info->hwave, - msg, info->user, param1, param2); + WINMM_CBInfo *info; + DWORD cb_type; + + EnterCriticalSection(&device->lock); + info = &device->cb_info; + /* Make a copy of this to pass to the callback while within the critical section, + * since it can be mutated once we leave the critical section. */ + cb_type = info->flags; + + if (info->flags & CB_RUNNING) { + switch (msg) { + case WIM_DATA: + case WOM_DONE: + param1->lpNext = NULL; + if (!info->first) { + info->first = info->last = param1; + } else { + info->last->lpNext = param1; + info->last = param1; + } + break; + case WIM_CLOSE: + case WOM_CLOSE: + /* If this device is closing, it might get reopened as another kind of device + * with all the buffered messages wiped, causing them to never get sent. + * So we need to send them all now. + * + * On Windows, calling `waveOutClose` from within `DriverCallback` hangs anyway, + * so this causing reentrancy isn't really a problem. */ + WINMM_SendBufferedMessages(device); + + LeaveCriticalSection(&device->lock); + + DriverCallback(info->callback, cb_type, (HDRVR)info->hwave, + msg, info->user, (DWORD_PTR)param1, (DWORD_PTR)param2); + + EnterCriticalSection(&device->lock); + break; + } + } else { + if ((info->flags & DCB_TYPEMASK) == DCB_FUNCTION) { + info->flags |= CB_RUNNING; + } + + LeaveCriticalSection(&device->lock); + + DriverCallback(info->callback, cb_type, (HDRVR)info->hwave, + msg, info->user, (DWORD_PTR)param1, (DWORD_PTR)param2); + + EnterCriticalSection(&device->lock); + + if ((info->flags & DCB_TYPEMASK) == DCB_FUNCTION) { + WINMM_SendBufferedMessages(device); + + info->flags &= ~CB_RUNNING; + } + } + + LeaveCriticalSection(&device->lock); }
static MMRESULT hr2mmr(HRESULT hr) @@ -1202,6 +1306,8 @@ static LRESULT WINMM_OpenDevice(WINMM_Device *device, WINMM_OpenInfo *info, device->cb_info.callback = info->callback; device->cb_info.user = info->cb_user; device->cb_info.hwave = device->handle; + device->cb_info.first = NULL; + device->cb_info.last = NULL;
info->handle = device->handle;
@@ -1623,7 +1729,6 @@ static WAVEHDR *WOD_MarkDoneHeaders(WINMM_Device *device)
static void WOD_PushData(WINMM_Device *device) { - WINMM_CBInfo cb_info; HRESULT hr; UINT32 pad, bufsize, avail_frames, queue_frames, written, ofs; UINT32 queue_bytes, nloops; @@ -1752,15 +1857,13 @@ static void WOD_PushData(WINMM_Device *device) device->played_frames += avail_frames;
exit: - cb_info = device->cb_info; - LeaveCriticalSection(&device->lock);
while(first){ WAVEHDR *next = first->lpNext; first->dwFlags &= ~WHDR_INQUEUE; first->dwFlags |= WHDR_DONE; - WINMM_NotifyClient(&cb_info, WOM_DONE, (DWORD_PTR)first, 0); + WINMM_NotifyClient(device, WOM_DONE, first, 0); first = next; } } @@ -1860,7 +1963,6 @@ static void WID_PullACMData(WINMM_Device *device)
static void WID_PullData(WINMM_Device *device) { - WINMM_CBInfo cb_info; WAVEHDR *queue, *first = NULL, *last = NULL; HRESULT hr;
@@ -1926,8 +2028,6 @@ static void WID_PullData(WINMM_Device *device) }
exit: - cb_info = device->cb_info; - LeaveCriticalSection(&device->lock);
if(last){ @@ -1936,7 +2036,7 @@ exit: WAVEHDR *next = first->lpNext; first->dwFlags &= ~WHDR_INQUEUE; first->dwFlags |= WHDR_DONE; - WINMM_NotifyClient(&cb_info, WIM_DATA, (DWORD_PTR)first, 0); + WINMM_NotifyClient(device, WIM_DATA, first, 0); first = next; } } @@ -1985,7 +2085,6 @@ static LRESULT WINMM_Pause(WINMM_Device *device)
static LRESULT WINMM_Reset(HWAVE hwave) { - WINMM_CBInfo cb_info; WINMM_Device *device = WINMM_GetDeviceFromHWAVE(hwave); BOOL is_out; WAVEHDR *first; @@ -2012,7 +2111,6 @@ static LRESULT WINMM_Reset(HWAVE hwave) device->last_clock_pos = 0; IAudioClient_Reset(device->client);
- cb_info = device->cb_info; is_out = device->render != NULL;
LeaveCriticalSection(&device->lock); @@ -2022,9 +2120,9 @@ static LRESULT WINMM_Reset(HWAVE hwave) first->dwFlags &= ~WHDR_INQUEUE; first->dwFlags |= WHDR_DONE; if(is_out) - WINMM_NotifyClient(&cb_info, WOM_DONE, (DWORD_PTR)first, 0); + WINMM_NotifyClient(device, WOM_DONE, first, 0); else - WINMM_NotifyClient(&cb_info, WIM_DATA, (DWORD_PTR)first, 0); + WINMM_NotifyClient(device, WIM_DATA, first, 0); first = next; }
@@ -2732,7 +2830,7 @@ MMRESULT WINAPI waveOutOpen(LPHWAVEOUT lphWaveOut, UINT uDeviceID, { LRESULT res; WINMM_OpenInfo info; - WINMM_CBInfo cb_info; + WINMM_Device* device;
TRACE("(%p, %u, %p, %lx, %lx, %08x)\n", lphWaveOut, uDeviceID, lpFormat, dwCallback, dwInstance, dwFlags); @@ -2763,12 +2861,9 @@ MMRESULT WINAPI waveOutOpen(LPHWAVEOUT lphWaveOut, UINT uDeviceID, if(lphWaveOut) *lphWaveOut = (HWAVEOUT)info.handle;
- cb_info.flags = HIWORD(dwFlags & CALLBACK_TYPEMASK); - cb_info.callback = dwCallback; - cb_info.user = dwInstance; - cb_info.hwave = info.handle; + device = WINMM_GetDeviceFromHWAVE(info.handle);
- WINMM_NotifyClient(&cb_info, WOM_OPEN, 0, 0); + WINMM_NotifyClient(device, WOM_OPEN, 0, 0);
return res; } @@ -2780,7 +2875,6 @@ UINT WINAPI waveOutClose(HWAVEOUT hWaveOut) { UINT res; WINMM_Device *device; - WINMM_CBInfo cb_info;
TRACE("(%p)\n", hWaveOut);
@@ -2789,14 +2883,12 @@ UINT WINAPI waveOutClose(HWAVEOUT hWaveOut) if(!WINMM_ValidateAndLock(device)) return MMSYSERR_INVALHANDLE;
- cb_info = device->cb_info; - LeaveCriticalSection(&device->lock);
res = SendMessageW(g_devices_hwnd, WODM_CLOSE, (WPARAM)hWaveOut, 0);
if(res == MMSYSERR_NOERROR) - WINMM_NotifyClient(&cb_info, WOM_CLOSE, 0, 0); + WINMM_NotifyClient(device, WOM_CLOSE, 0, 0);
return res; } @@ -3388,7 +3480,7 @@ MMRESULT WINAPI waveInOpen(HWAVEIN* lphWaveIn, UINT uDeviceID, { LRESULT res; WINMM_OpenInfo info; - WINMM_CBInfo cb_info; + WINMM_Device* device;
TRACE("(%p, %x, %p, %lx, %lx, %08x)\n", lphWaveIn, uDeviceID, lpFormat, dwCallback, dwInstance, dwFlags); @@ -3419,12 +3511,9 @@ MMRESULT WINAPI waveInOpen(HWAVEIN* lphWaveIn, UINT uDeviceID, if(lphWaveIn) *lphWaveIn = (HWAVEIN)info.handle;
- cb_info.flags = HIWORD(dwFlags & CALLBACK_TYPEMASK); - cb_info.callback = dwCallback; - cb_info.user = dwInstance; - cb_info.hwave = info.handle; + device = WINMM_GetDeviceFromHWAVE(info.handle);
- WINMM_NotifyClient(&cb_info, WIM_OPEN, 0, 0); + WINMM_NotifyClient(device, WIM_OPEN, 0, 0);
return res; } @@ -3435,7 +3524,6 @@ MMRESULT WINAPI waveInOpen(HWAVEIN* lphWaveIn, UINT uDeviceID, UINT WINAPI waveInClose(HWAVEIN hWaveIn) { WINMM_Device *device; - WINMM_CBInfo cb_info; UINT res;
TRACE("(%p)\n", hWaveIn); @@ -3445,14 +3533,12 @@ UINT WINAPI waveInClose(HWAVEIN hWaveIn) if(!WINMM_ValidateAndLock(device)) return MMSYSERR_INVALHANDLE;
- cb_info = device->cb_info; - LeaveCriticalSection(&device->lock);
res = SendMessageW(g_devices_hwnd, WIDM_CLOSE, (WPARAM)hWaveIn, 0);
if(res == MMSYSERR_NOERROR) - WINMM_NotifyClient(&cb_info, WIM_CLOSE, 0, 0); + WINMM_NotifyClient(device, WIM_CLOSE, 0, 0);
return res; } @@ -3568,7 +3654,6 @@ UINT WINAPI waveInStart(HWAVEIN hWaveIn) */ UINT WINAPI waveInStop(HWAVEIN hWaveIn) { - WINMM_CBInfo cb_info; WINMM_Device *device; WAVEHDR *buf; HRESULT hr; @@ -3593,14 +3678,12 @@ UINT WINAPI waveInStop(HWAVEIN hWaveIn) }else buf = NULL;
- cb_info = device->cb_info; - LeaveCriticalSection(&device->lock);
if(buf){ buf->dwFlags &= ~WHDR_INQUEUE; buf->dwFlags |= WHDR_DONE; - WINMM_NotifyClient(&cb_info, WIM_DATA, (DWORD_PTR)buf, 0); + WINMM_NotifyClient(device, WIM_DATA, buf, 0); }
return MMSYSERR_NOERROR;