Make SetWindowSubclass behave with SetWindowLong subclasses.

Allow unlimited number of subclasses.
Correct issue when SendMessage is called from within a subclass proc.
Add regression test.
This commit is contained in:
Kevin Koltzau 2004-08-22 22:29:37 +00:00 committed by Alexandre Julliard
parent 6b1e83281b
commit 59302aed35
5 changed files with 425 additions and 100 deletions

View File

@ -146,17 +146,19 @@ BOOL Str_SetPtrAtoW (LPWSTR *lppDest, LPCSTR lpSrc);
#define WINE_FILEVERSIONSTR "5.80"
/* Our internal stack structure of the window procedures to subclass */
typedef struct _SUBCLASSPROCS {
SUBCLASSPROC subproc;
UINT_PTR id;
DWORD_PTR ref;
struct _SUBCLASSPROCS *next;
} SUBCLASSPROCS, *LPSUBCLASSPROCS;
typedef struct
{
struct {
SUBCLASSPROC subproc;
UINT_PTR id;
DWORD_PTR ref;
} SubclassProcs[31];
int stackpos;
int stacknum;
int wndprocrecursion;
SUBCLASSPROCS *SubclassProcs;
SUBCLASSPROCS *stackpos;
WNDPROC origproc;
BOOL running;
} SUBCLASS_INFO, *LPSUBCLASS_INFO;
/* undocumented functions */

View File

@ -116,6 +116,7 @@ extern void TREEVIEW_Unregister(void);
extern void UPDOWN_Register(void);
extern void UPDOWN_Unregister(void);
LRESULT WINAPI COMCTL32_SubclassProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
LPSTR COMCTL32_aSubclass = NULL;
HMODULE COMCTL32_hModule = 0;
@ -1092,7 +1093,7 @@ BOOL WINAPI SetWindowSubclass (HWND hWnd, SUBCLASSPROC pfnSubclass,
UINT_PTR uIDSubclass, DWORD_PTR dwRef)
{
LPSUBCLASS_INFO stack;
int n;
LPSUBCLASSPROCS proc;
TRACE ("(%p, %p, %x, %lx)\n", hWnd, pfnSubclass, uIDSubclass, dwRef);
@ -1116,47 +1117,42 @@ BOOL WINAPI SetWindowSubclass (HWND hWnd, SUBCLASSPROC pfnSubclass,
/* set window procedure to our own and save the current one */
if (IsWindowUnicode (hWnd))
stack->origproc = (WNDPROC)SetWindowLongW (hWnd, GWL_WNDPROC,
(LONG)DefSubclassProc);
(LONG)COMCTL32_SubclassProc);
else
stack->origproc = (WNDPROC)SetWindowLongA (hWnd, GWL_WNDPROC,
(LONG)DefSubclassProc);
} else {
WNDPROC current;
if (IsWindowUnicode (hWnd))
current = (WNDPROC)GetWindowLongW (hWnd, GWL_WNDPROC);
else
current = (WNDPROC)GetWindowLongA (hWnd, GWL_WNDPROC);
if (current != DefSubclassProc) {
ERR ("Application has subclassed with our procedure, then manually, then with us again. The current implementation can't handle this.\n");
return FALSE;
(LONG)COMCTL32_SubclassProc);
}
else {
/* Check to see if we have called this function with the same uIDSubClass
* and pfnSubclass */
proc = stack->SubclassProcs;
while (proc) {
if ((proc->id == uIDSubclass) &&
(proc->subproc == pfnSubclass)) {
proc->ref = dwRef;
return TRUE;
}
proc = proc->next;
}
}
/* Check to see if we have called this function with the same uIDSubClass
* and pfnSubclass */
for (n = 0; n < stack->stacknum; n++)
if ((stack->SubclassProcs[n].id == uIDSubclass) &&
(stack->SubclassProcs[n].subproc == pfnSubclass)) {
stack->SubclassProcs[n].ref = dwRef;
return TRUE;
}
if (stack->stacknum >= 32) {
ERR ("We have a Subclass stack overflow, please increment size\n");
proc = HeapAlloc(GetProcessHeap(), 0, sizeof(SUBCLASSPROCS));
if (!proc) {
ERR ("Failed to allocate subclass entry in stack\n");
if (IsWindowUnicode (hWnd))
SetWindowLongW (hWnd, GWL_WNDPROC, (LONG)stack->origproc);
else
SetWindowLongA (hWnd, GWL_WNDPROC, (LONG)stack->origproc);
HeapFree (GetProcessHeap (), 0, stack);
RemovePropA( hWnd, COMCTL32_aSubclass );
return FALSE;
}
memmove (&stack->SubclassProcs[1], &stack->SubclassProcs[0],
sizeof(stack->SubclassProcs[0]) * stack->stacknum);
stack->stacknum++;
if (stack->wndprocrecursion)
stack->stackpos++;
stack->SubclassProcs[0].subproc = pfnSubclass;
stack->SubclassProcs[0].ref = dwRef;
stack->SubclassProcs[0].id = uIDSubclass;
proc->subproc = pfnSubclass;
proc->ref = dwRef;
proc->id = uIDSubclass;
proc->next = stack->SubclassProcs;
stack->SubclassProcs = proc;
return TRUE;
}
@ -1182,7 +1178,7 @@ BOOL WINAPI GetWindowSubclass (HWND hWnd, SUBCLASSPROC pfnSubclass,
UINT_PTR uID, DWORD_PTR *pdwRef)
{
LPSUBCLASS_INFO stack;
int n;
LPSUBCLASSPROCS proc;
TRACE ("(%p, %p, %x, %p)\n", hWnd, pfnSubclass, uID, pdwRef);
@ -1191,12 +1187,15 @@ BOOL WINAPI GetWindowSubclass (HWND hWnd, SUBCLASSPROC pfnSubclass,
if (!stack)
return FALSE;
for (n = 0; n < stack->stacknum; n++)
if ((stack->SubclassProcs[n].id == uID) &&
(stack->SubclassProcs[n].subproc == pfnSubclass)) {
*pdwRef = stack->SubclassProcs[n].ref;
proc = stack->SubclassProcs;
while (proc) {
if ((proc->id == uID) &&
(proc->subproc == pfnSubclass)) {
*pdwRef = proc->ref;
return TRUE;
}
proc = proc->next;
}
return FALSE;
}
@ -1220,7 +1219,9 @@ BOOL WINAPI GetWindowSubclass (HWND hWnd, SUBCLASSPROC pfnSubclass,
BOOL WINAPI RemoveWindowSubclass(HWND hWnd, SUBCLASSPROC pfnSubclass, UINT_PTR uID)
{
LPSUBCLASS_INFO stack;
int n;
LPSUBCLASSPROCS prevproc = NULL;
LPSUBCLASSPROCS proc;
BOOL ret = FALSE;
TRACE ("(%p, %p, %x)\n", hWnd, pfnSubclass, uID);
@ -1229,8 +1230,28 @@ BOOL WINAPI RemoveWindowSubclass(HWND hWnd, SUBCLASSPROC pfnSubclass, UINT_PTR u
if (!stack)
return FALSE;
if ((stack->stacknum == 1) && (stack->stackpos == 1) &&
!stack->wndprocrecursion) {
proc = stack->SubclassProcs;
while (proc) {
if ((proc->id == uID) &&
(proc->subproc == pfnSubclass)) {
if (!prevproc)
stack->SubclassProcs = proc->next;
else
prevproc->next = proc->next;
if (stack->stackpos == proc)
stack->stackpos = stack->stackpos->next;
HeapFree (GetProcessHeap (), 0, proc);
ret = TRUE;
break;
}
prevproc = proc;
proc = proc->next;
}
if (!stack->SubclassProcs && !stack->running) {
TRACE("Last Subclass removed, cleaning up\n");
/* clean up our heap and reset the origional window procedure */
if (IsWindowUnicode (hWnd))
@ -1239,30 +1260,51 @@ BOOL WINAPI RemoveWindowSubclass(HWND hWnd, SUBCLASSPROC pfnSubclass, UINT_PTR u
SetWindowLongA (hWnd, GWL_WNDPROC, (LONG)stack->origproc);
HeapFree (GetProcessHeap (), 0, stack);
RemovePropA( hWnd, COMCTL32_aSubclass );
return TRUE;
}
for (n = stack->stacknum - 1; n >= 0; n--)
if ((stack->SubclassProcs[n].id == uID) &&
(stack->SubclassProcs[n].subproc == pfnSubclass)) {
if (n != stack->stacknum)
/* Fill the hole in the stack */
memmove (&stack->SubclassProcs[n], &stack->SubclassProcs[n + 1],
sizeof(stack->SubclassProcs[0]) * (stack->stacknum - n));
stack->SubclassProcs[n].subproc = NULL;
stack->SubclassProcs[n].ref = 0;
stack->SubclassProcs[n].id = 0;
stack->stacknum--;
if (n < stack->stackpos && stack->wndprocrecursion)
stack->stackpos--;
return TRUE;
}
return FALSE;
return ret;
}
/***********************************************************************
* COMCTL32_SubclassProc (internal)
*
* Window procedure for all subclassed windows.
* Saves the current subclassing stack position to support nested messages
*/
LRESULT WINAPI COMCTL32_SubclassProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LPSUBCLASS_INFO stack;
LPSUBCLASSPROCS proc;
LRESULT ret;
TRACE ("(%p, 0x%08x, 0x%08x, 0x%08lx)\n", hWnd, uMsg, wParam, lParam);
stack = (LPSUBCLASS_INFO)GetPropA (hWnd, COMCTL32_aSubclass);
if (!stack) {
ERR ("Our sub classing stack got erased for %p!! Nothing we can do\n", hWnd);
return 0;
}
/* Save our old stackpos to properly handle nested messages */
proc = stack->stackpos;
stack->stackpos = stack->SubclassProcs;
stack->running = TRUE;
ret = DefSubclassProc(hWnd, uMsg, wParam, lParam);
stack->running = FALSE;
stack->stackpos = proc;
if (!stack->SubclassProcs) {
TRACE("Last Subclass removed, cleaning up\n");
/* clean up our heap and reset the origional window procedure */
if (IsWindowUnicode (hWnd))
SetWindowLongW (hWnd, GWL_WNDPROC, (LONG)stack->origproc);
else
SetWindowLongA (hWnd, GWL_WNDPROC, (LONG)stack->origproc);
HeapFree (GetProcessHeap (), 0, stack);
RemovePropA( hWnd, COMCTL32_aSubclass );
}
return ret;
}
/***********************************************************************
* DefSubclassProc [COMCTL32.413]
@ -1285,6 +1327,8 @@ LRESULT WINAPI DefSubclassProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPar
LPSUBCLASS_INFO stack;
LRESULT ret;
TRACE ("(%p, 0x%08x, 0x%08x, 0x%08lx)\n", hWnd, uMsg, wParam, lParam);
/* retrieve our little stack from the Properties */
stack = (LPSUBCLASS_INFO)GetPropA (hWnd, COMCTL32_aSubclass);
if (!stack) {
@ -1292,41 +1336,20 @@ LRESULT WINAPI DefSubclassProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPar
return 0;
}
stack->wndprocrecursion++;
/* If we are at the end of stack then we have to call the original
* window procedure */
if (stack->stackpos == stack->stacknum) {
if (!stack->stackpos) {
if (IsWindowUnicode (hWnd))
ret = CallWindowProcW (stack->origproc, hWnd, uMsg, wParam, lParam);
else
ret = CallWindowProcA (stack->origproc, hWnd, uMsg, wParam, lParam);
} else {
stack->stackpos++;
SUBCLASSPROCS proc;
memcpy(&proc, stack->stackpos, sizeof(proc));
stack->stackpos = stack->stackpos->next;
/* call the Subclass procedure from the stack */
ret = stack->SubclassProcs[stack->stackpos - 1].subproc (hWnd, uMsg, wParam, lParam,
stack->SubclassProcs[stack->stackpos - 1].id, stack->SubclassProcs[stack->stackpos - 1].ref);
stack->stackpos--;
}
/* We finished the recursion, so let's reinitalize the stack position to
* beginning */
if ((--stack->wndprocrecursion) == 0) {
stack->stackpos = 0;
}
/* If we removed the last entry in our stack while a window procedure was
* running then we have to clean up */
if ((stack->stackpos == 0) && (stack->stacknum == 0)) {
TRACE("Last Subclass removed, cleaning up\n");
/* clean up our heap and reset the origional window procedure */
if (IsWindowUnicode (hWnd))
SetWindowLongW (hWnd, GWL_WNDPROC, (LONG)stack->origproc);
else
SetWindowLongA (hWnd, GWL_WNDPROC, (LONG)stack->origproc);
HeapFree (GetProcessHeap (), 0, stack);
RemovePropA( hWnd, COMCTL32_aSubclass );
return TRUE;
ret = proc.subproc (hWnd, uMsg, wParam, lParam,
proc.id, proc.ref);
}
return ret;

View File

@ -1,5 +1,6 @@
Makefile
dpa.ok
imagelist.ok
subclass.ok
tab.ok
testlist.c

View File

@ -8,6 +8,7 @@ IMPORTS = comctl32 user32 gdi32
CTESTS = \
dpa.c \
imagelist.c \
subclass.c \
tab.c
@MAKE_TEST_RULES@

View File

@ -0,0 +1,298 @@
/* Unit tests for subclassed windows.
*
* Copyright 2004 Kevin Koltzau
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <assert.h>
#include <stdarg.h>
#define _WIN32_WINNT 0x0501 /* For SetWindowSubclass/etc */
#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "winuser.h"
#include "commctrl.h"
#include "wine/test.h"
static BOOL (WINAPI *pSetWindowSubclass)(HWND, SUBCLASSPROC, UINT_PTR, DWORD_PTR);
static BOOL (WINAPI *pRemoveWindowSubclass)(HWND, SUBCLASSPROC, UINT_PTR);
static LRESULT (WINAPI *pDefSubclassProc)(HWND, UINT, WPARAM, LPARAM);
#define SEND_NEST 0x01
#define DELETE_SELF 0x02
#define DELETE_PREV 0x04
struct message {
int procnum; /* WndProc id message is expected from */
WPARAM wParam; /* expected value of wParam */
};
static int sequence_cnt, sequence_size;
static struct message* sequence;
static const struct message Sub_BasicTest[] = {
{ 2, 1 },
{ 1, 1 },
{ 2, 2 },
{ 1, 2 },
{ 0 }
};
static const struct message Sub_DeletedTest[] = {
{ 2, 1 },
{ 1, 1 },
{ 0 }
};
static const struct message Sub_AfterDeletedTest[] = {
{ 1, 1 },
{ 0 }
};
static const struct message Sub_OldAfterNewTest[] = {
{ 3, 1 },
{ 2, 1 },
{ 1, 1 },
{ 3, 2 },
{ 2, 2 },
{ 1, 2 },
{ 0 }
};
static const struct message Sub_MixTest[] = {
{ 3, 1 },
{ 4, 1 },
{ 2, 1 },
{ 1, 1 },
{ 0 }
};
static const struct message Sub_MixAndNestTest[] = {
{ 3, 1 },
{ 4, 1 },
{ 3, 2 },
{ 4, 2 },
{ 2, 2 },
{ 1, 2 },
{ 2, 1 },
{ 1, 1 },
{ 0 }
};
static const struct message Sub_MixNestDelTest[] = {
{ 3, 1 },
{ 4, 1 },
{ 3, 2 },
{ 2, 2 },
{ 1, 2 },
{ 2, 1 },
{ 1, 1 },
{ 0 }
};
static const struct message Sub_MixDelPrevTest[] = {
{ 3, 1 },
{ 5, 1 },
{ 2, 1 },
{ 1, 1 },
{ 0 }
};
static void add_message(const struct message *msg)
{
if (!sequence)
{
sequence_size = 10;
sequence = HeapAlloc( GetProcessHeap(), 0, sequence_size * sizeof (struct message) );
}
if (sequence_cnt == sequence_size)
{
sequence_size *= 2;
sequence = HeapReAlloc( GetProcessHeap(), 0, sequence, sequence_size * sizeof (struct message) );
}
assert(sequence);
sequence[sequence_cnt].wParam = msg->wParam;
sequence[sequence_cnt].procnum = msg->procnum;
sequence_cnt++;
}
static void flush_sequence()
{
HeapFree(GetProcessHeap(), 0, sequence);
sequence = 0;
sequence_cnt = sequence_size = 0;
}
static void ok_sequence(const struct message *expected, const char *context)
{
static const struct message end_of_sequence = { 0, 0 };
const struct message *actual;
add_message(&end_of_sequence);
actual = sequence;
while(expected->procnum && actual->procnum)
{
ok(expected->procnum == actual->procnum,
"%s: the procnum %d was expected, but got procnum %d instead\n",
context, expected->procnum, actual->procnum);
ok(expected->wParam == actual->wParam,
"%s: in procnum %d expecting wParam 0x%x got 0x%x\n",
context, expected->procnum, expected->wParam, actual->wParam);
expected++;
actual++;
}
ok(!expected->procnum, "Recieved less messages then expected\n");
ok(!actual->procnum, "Recieved more messages then expected\n");
flush_sequence();
}
static LRESULT WINAPI WndProc1(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
struct message msg;
if(message == WM_USER) {
msg.wParam = wParam;
msg.procnum = 1;
add_message(&msg);
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
static WNDPROC origProc3;
static LRESULT WINAPI WndProc3(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
struct message msg;
if(message == WM_USER) {
msg.wParam = wParam;
msg.procnum = 3;
add_message(&msg);
}
return CallWindowProc(origProc3, hwnd, message, wParam, lParam);
}
static LRESULT WINAPI WndProcSub(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, UINT_PTR uldSubclass, DWORD_PTR dwRefData)
{
struct message msg;
if(message == WM_USER) {
msg.wParam = wParam;
msg.procnum = uldSubclass;
add_message(&msg);
if(lParam) {
if(dwRefData & DELETE_SELF) {
pRemoveWindowSubclass(hwnd, WndProcSub, uldSubclass);
pRemoveWindowSubclass(hwnd, WndProcSub, uldSubclass);
}
if(dwRefData & DELETE_PREV)
pRemoveWindowSubclass(hwnd, WndProcSub, uldSubclass-1);
if(dwRefData & SEND_NEST)
SendMessage(hwnd, WM_USER, wParam+1, 0);
}
}
return pDefSubclassProc(hwnd, message, wParam, lParam);
}
static void test_subclass()
{
HWND hwnd = CreateWindowExA(0, "TestSubclass", "Test subclass", WS_OVERLAPPEDWINDOW,
100, 100, 200, 200, 0, 0, 0, NULL);
assert(hwnd);
pSetWindowSubclass(hwnd, WndProcSub, 2, 0);
SendMessage(hwnd, WM_USER, 1, 0);
SendMessage(hwnd, WM_USER, 2, 0);
ok_sequence(Sub_BasicTest, "Basic");
pSetWindowSubclass(hwnd, WndProcSub, 2, DELETE_SELF);
SendMessage(hwnd, WM_USER, 1, 1);
ok_sequence(Sub_DeletedTest, "Deleted");
SendMessage(hwnd, WM_USER, 1, 0);
ok_sequence(Sub_AfterDeletedTest, "After Deleted");
pSetWindowSubclass(hwnd, WndProcSub, 2, 0);
origProc3 = (WNDPROC)SetWindowLong(hwnd, GWL_WNDPROC, (LONG)WndProc3);
SendMessage(hwnd, WM_USER, 1, 0);
SendMessage(hwnd, WM_USER, 2, 0);
ok_sequence(Sub_OldAfterNewTest, "Old after New");
pSetWindowSubclass(hwnd, WndProcSub, 4, 0);
SendMessage(hwnd, WM_USER, 1, 0);
ok_sequence(Sub_MixTest, "Mix");
/* Now the fun starts */
pSetWindowSubclass(hwnd, WndProcSub, 4, SEND_NEST);
SendMessage(hwnd, WM_USER, 1, 1);
ok_sequence(Sub_MixAndNestTest, "Mix and nest");
pSetWindowSubclass(hwnd, WndProcSub, 4, SEND_NEST | DELETE_SELF);
SendMessage(hwnd, WM_USER, 1, 1);
ok_sequence(Sub_MixNestDelTest, "Mix, nest, del");
pSetWindowSubclass(hwnd, WndProcSub, 4, 0);
pSetWindowSubclass(hwnd, WndProcSub, 5, DELETE_PREV);
SendMessage(hwnd, WM_USER, 1, 1);
ok_sequence(Sub_MixDelPrevTest, "Mix and del prev");
DestroyWindow(hwnd);
}
static BOOL RegisterWindowClasses(void)
{
WNDCLASSA cls;
cls.style = 0;
cls.lpfnWndProc = WndProc1;
cls.cbClsExtra = 0;
cls.cbWndExtra = 0;
cls.hInstance = GetModuleHandleA(0);
cls.hIcon = 0;
cls.hCursor = NULL;
cls.hbrBackground = NULL;
cls.lpszMenuName = NULL;
cls.lpszClassName = "TestSubclass";
if(!RegisterClassA(&cls)) return FALSE;
return TRUE;
}
START_TEST(subclass)
{
HMODULE hdll;
hdll = GetModuleHandleA("comctl32.dll");
assert(hdll);
pSetWindowSubclass = (void*)GetProcAddress(hdll, "SetWindowSubclass");
pRemoveWindowSubclass = (void*)GetProcAddress(hdll, "RemoveWindowSubclass");
pDefSubclassProc = (void*)GetProcAddress(hdll, "DefSubclassProc");
if(!pSetWindowSubclass || !pRemoveWindowSubclass || !pDefSubclassProc)
return;
if(!RegisterWindowClasses()) assert(0);
test_subclass();
}