[PATCH v3 2/2] winmm: Buffer any WOM_DONE/WIM_DATA events triggered from within a waveOut/waveIn callback.

Liam Murphy liampm32 at gmail.com
Thu Jan 20 22:37:27 CST 2022


The events are then fired after the callback completes.

Signed-off-by: Liam Murphy <liampm32 at 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;
-- 
2.34.1




More information about the wine-devel mailing list