Properly using SetWindowLong()...

Hi,

In an effort to avoid global variables in my program, im trying to use SetWindowLong to store a pointer to an instance of a normal class. I create an instance ofthe class in WinMain, because I have to use it in message loop.

1
2
3
4
AppClass * app = 0;
app = new App;
if (app)
    ::SetWindowLong( hwnd, 0, (long)app);


ButI also have to use it in the window procedure...
1
2
3
4
5
6
AppClass* app = 0;
switch (msg)
{
case WM_CREATE:
    app = (AppClass*)::GetWindowLong(hwnd, 0);
}


I keep getting an access violation when I use it in the procedure, so I must obviously be doing something wrong. I have allocated 8 bytes in the cdWndExtra in the WNDCLASSEX struct.

Thank you for any help!
closed account (z05DSL3A)
Try:
::SetWindowLong( hwnd, GWL_USERDATA, (long)app);
and
::GetWindowLong(hwnd, GWL_USERDATA);

GWL_USERDATA is equal to -21
That appeared to work in terms of being able to call functions from the class. But it appears that when retrieving the pointer none of the private data members of the class have been initialized. Which is strange because they are initialized fine in WinMain, and I can use them. The debugger breaks and says access reading violation, and all the data members either cannot be evaluated or are not found, with exclamation marks next to them. For instance, in response to WM_KEYDOWN:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//in procedure...
case: WM_KEYDOWN:
    app->keyboard_handler( wparam );
    break;

//and then the definition of keyboard handler in the AppClass is like this:
void AppClass::keyboard_handler( int key )
{
    if (key == VK_ESCAPE)
    {
        if (_game_running)
        {
            //more code here
        }
    }
}


the debugger says that _game_running does not exist (hence access reading violation) but it does exist and has been set in win_main?? Something is still wrong!??
closed account (z05DSL3A)
Can you post some more code? WinMain() would be a good place to start.

Edit:
and WndProc()
Last edited on
Sure, I have several wrappers for the winapi, but they are rather straightforward.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
int WINAPI WinMain(
	HINSTANCE inst,
	HINSTANCE prev_inst,
	LPSTR lp_cmd_line,
	int n_cmd_show
	)
{
	core::WndClassEx winClass(
        _T("wcex"),
        inst,
        win_proc
    );

    if (! winClass.register_class())
        return 1;

    core::WndCreator window( winClass, _T("test") );
    window.set_width( 300, 200 );
    window.add_system_menu();
    window.add_minimize_box();
    if (! window.create_window())
        return 1;

    window.show_window();

	AppClass *app = 0;
	app = new AppClass;
	if (app)
		::SetWindowLong( window.get_window_handle(), GWL_USERDATA, (long)app);
	MSG msg;
	while (true)
	{
		if (::PeekMessage( &msg, 0, 0, 0, PM_REMOVE ))
		{
			if (msg.message == WM_QUIT)
				break;

			::TranslateMessage(&msg);
			::DispatchMessage(&msg);
		}
		else
		{

		}
	}

	return static_cast<int>(msg.wParam);
}


and the windproc...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
LRESULT CALLBACK win_proc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam )
{
	AppClass* app = 0;
	switch (msg)
	{
	case WM_CREATE:
		{
			app = (AppClass*)::GetWindowLong( hwnd, GWL_USERDATA );
		}
		break;
	case WM_PAINT:
		{
			PAINTSTRUCT ps;
			HDC hdc = ::BeginPaint( hwnd, &ps );

			::EndPaint( hwnd, &ps );
		}
		break;
	case WM_KEYDOWN:
		{
			app->keyboard_handler( wparam );
		}
		break;
	case WM_CLOSE:
		{
			if (app)
				delete app;
			::DestroyWindow( hwnd );
		}
		break;
	case WM_DESTROY:
		{
			::PostQuitMessage( 0 );
		}
		break;
	}

	return ::DefWindowProc( hwnd, msg, wparam, lparam );
}
Why are you using deprecated functions like SetWindowLong() ? They will crash if you want to compile your app as 64-bit.
::SetWindowLongPtr( hwnd, GWLP_USERDATA, (LONG_PTR)app);
and
::GetWindowLongPtr(hwnd, GWLP_USERDATA);

Last edited on
Inside the window procedure, the variable app has to be static, or you must retrieve the pointer in every call to the Window Procedure.
I was not aware that they were deprecated. I changed them, as well as making app static, and to no avail, I still get an access reading violation.
Debug your code. Make sure that app != NULL before using it in every message received and processed. Make sure your variable declaration looks just like this: static AppClass *app = 0;
I made srue that it was static, but after retrieving it in WM_CREATE it is null, so its not even being set right in WinMain(). Ive been debugging it all along and I just can't figure it out. I even removed all my wrappers and manually filled out the WNDCLASSEX, and manually created the window.
Here is my updated code. I am probably missing something very basic.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
int WINAPI WinMain(
	HINSTANCE inst,
	HINSTANCE prev_inst,
	LPSTR lp_cmd_line,
	int n_cmd_show
	)
{
	WNDCLASSEX wcex;
	wcex.cbSize	        = sizeof( WNDCLASSEX );
	wcex.style	        = CS_VREDRAW | CS_HREDRAW;
	wcex.lpfnWndProc        = win_proc;
	wcex.cbClsExtra	        = 0;
	wcex.cbWndExtra		= 8;
	wcex.hInstance		= inst;
	wcex.hIcon		= 0;
	wcex.hCursor		= ::LoadCursor( NULL, IDC_ARROW );
	wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW + 1);
	wcex.lpszMenuName	= 0;
	wcex.lpszClassName	= "wcex";
	wcex.hIconSm		= 0;

	if (!::RegisterClassEx( &wcex ))
		return 1;
	HWND hwnd = 0;

	
	hwnd = ::CreateWindowEx(
		0, "wcex", "test",
		WS_OVERLAPPEDWINDOW,
		0, 0, 300, 200,
		0, 0, inst, 0
                );
	if (hwnd == 0)
		return 1;

	AppClass *app = 0;
	app = new AppClass(5);
	if (app)
		::SetWindowLongPtr( hwnd, GWLP_USERDATA, (LONG_PTR)app);
	else
		return 0;

	::ShowWindow( hwnd, SW_SHOW );
	::UpdateWindow( hwnd );
	MSG msg;
	while (true)
	{
		if (::PeekMessage( &msg, 0, 0, 0, PM_REMOVE ))
		{
			if (msg.message == WM_QUIT)
				break;

			::TranslateMessage(&msg);
			::DispatchMessage(&msg);
		}
		else
		{

		}
	}

	return static_cast<int>(msg.wParam);
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
LRESULT CALLBACK win_proc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam )
{
	static AppClass* app = 0;
	switch (msg)
	{
	case WM_CREATE:
		{
			app = (AppClass*)::GetWindowLongPtr( hwnd, GWLP_USERDATA );

			if (!app) //cant get past this point
			{
				::MessageBox( hwnd, "Unable to retrieve pointer.", 0, 0 );
				::SendMessage( hwnd, WM_CLOSE, 0, 0 );
			}
		}
		break;
	case WM_PAINT:
		{
			PAINTSTRUCT ps;
			HDC hdc = ::BeginPaint( hwnd, &ps );

			::EndPaint( hwnd, &ps );
		}
		break;
	case WM_KEYDOWN:
		{
			if (app)//this fails
				int i = app->get_number();
		}
		break;
	case WM_CLOSE:
		{
			if (app)
				delete app;
			::DestroyWindow( hwnd );
		}
		break;
	case WM_DESTROY:
		{
			::PostQuitMessage( 0 );
		}
		break;
	}

	return ::DefWindowProc( hwnd, msg, wparam, lparam );
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#ifndef APPCLASS_H
#define APPCLASS_H

#include "win_main.h"

class AppClass
{
public:

	AppClass( void )
		:	number( 5 )
	{}

	AppClass( int number )
		:	number( number )
	{

	}

	void set_number( int number )
	{
		number = number;
	}

	int get_number( void ) const
	{
		return number;
	}

private:

	int		number;

};

#endif 
closed account (z05DSL3A)
I think you are going to have to do a bit of trickery here.

You will have to pass an AppClass* via CreateWindowEx() using lpParam, and call SetWindowLongPtr() from your WndProc (handle WM_NCCREATE).


1
2
3
4
5
6
7
8
9
10
11
12
13
14
LRESULT CALLBACK win_proc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam )
{
    AppClass* app;
    if(msg == WM_NCCREATE)
    {
         app = (AppClass*) lparam;
         SetWindowLongPtr(hWnd, GWL_USERDATA, (LONG_PTR) Window_ptr);
    }
    else
        app = (AppClass*) ::GetWindowLongPtr( hwnd, GWLP_USERDATA );

    switch ....



Edit:
you may actually be able to get way with:
1
2
3
4
5
6
7
LRESULT CALLBACK win_proc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam )
{
	AppClass* app =(AppClass*)::GetWindowLongPtr( hwnd, GWLP_USERDATA );
	
        switch (msg)
	{
	

Last edited on
Why are you using wcex.cbWndExtra = 8; in winmain ? Never write constants like that in your code.
The size of a pointer is 4 bytes in win32 and 8 bytes in win64. Maybe this is a problem.
closed account (z05DSL3A)
The problem is that GetWindowLongPtr is being called before SetWindowLongPtr.

WM_CREATE is sent when an application requests that a window be created by calling the CreateWindowEx function. This is sent before the function returns. The window procedure receives this message after the window is created, but before the window becomes visible. The Window procedure calls GetWindowLongPtr() before the code in main gets to SetWindowLongPtr.
I see no problem using globals if your window procedure isn't object oriented anyway. As modoran says, if your using the extra window bytes, make sure you put sizeof:

wc.cbWndExtra = sizeof(AppClass*);

then to make it object oriented, you should have a base class which represents a window with a static WndProc and a virtual WndProc e.g:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Window class
// Represents basis of a window
class CWindow
{
	static LRESULT CALLBACK WndProcRouter(HWND,UINT,WPARAM,LPARAM);
	void AttachTo(HWND hwnd);
	
protected:
	HWND m_hwnd;
	static CWindow* PtrFromHandle(HWND hwnd);
	virtual LRESULT WndProc(UINT msg, WPARAM wParam, LPARAM lParam);
	static HWND CWindow::CreateGenericWindow(DWORD exStyle, LPCTSTR text, DWORD style, int x, int y, int width, int height, HWND parent, HMENU id);
	
public:
	static ATOM Register(HINSTANCE hInst);

	CWindow();
	~CWindow();

	HWND Handle();
	void Show(int nShow = SW_SHOW);
};


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Static window proc
LRESULT CALLBACK CWindow::WndProcRouter(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	CWindow *pwnd = NULL;

	if (msg == WM_NCCREATE)
	{
		pwnd = (CWindow*)((CREATESTRUCT*)lParam)->lpCreateParams;

		if (pwnd)
		{
			pwnd->AttachTo(hwnd);
			return DefWindowProc(hwnd, msg, wParam, lParam);
		}
		return FALSE;
	}

	pwnd = PtrFromHandle(hwnd);

	if (pwnd)
	{
		return pwnd->WndProc(msg, wParam, lParam);
	}
	return DefWindowProc(hwnd, msg, wParam, lParam);
}


Then inherit this class:
1
2
3
4
5
6
7
8
class TestWindow : public CWindow
{
	LRESULT WndProc(UINT msg, WPARAM wparam, LPARAM lparam);
	HBRUSH m_hbBack;

public:
	bool Create(HINSTANCE hInst);
};


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
LRESULT TestWindow::WndProc(UINT msg, WPARAM wparam, LPARAM lparam)
{
	switch (msg)
	{
	case WM_CREATE:
		m_hbBack = CreateSolidBrush(RGB(rand() % 255, rand() % 255, rand() % 255));
		return 0;

	case WM_ERASEBKGND: return 1;
	case WM_PAINT:
		{
			PAINTSTRUCT ps;
			BeginPaint(m_hwnd, &ps);

			FillRect(ps.hdc, &ps.rcPaint, m_hbBack);

			EndPaint(m_hwnd, &ps);
			return 0;
		}
	case WM_DESTROY:
		DeleteObject(m_hbBack);
		return 0;
	}

	return DefWindowProc(m_hwnd, msg, wparam, lparam);
}

Last edited on
I finally got it working. I thank you all for your help. I ended up passing a pointer to an instance of AppClass as the final paramter of CreateWindowEx(), and implemented this solution at the top of my window procedure:

1
2
3
4
5
6
7
8
9
10
11
12
AppClass *app;
if (msg == WM_CREATE)
{
    CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT*>(lparam);
    app = reinterpret_cast<AppClass*>(pCreate->lpCreateParams);
    SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)app);
}
else
{
    LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);
    app = reinterpret_cast<AppClass*>(ptr);
}


@Grey Wolf - You lead me right to it. Your answer caused me to find this:
http://msdn.microsoft.com/en-us/library/ff381400(VS.85).aspx

Therefore, I don't have to use the extra bytes in WNDCLASSEX.

@kaije - Your solution is interesting. I will certainly study it more. Global variables seem almost impossible to avoid now matter how much we wrap the WinApi. Atleast it is possible to avoid them. In future applications, I will use only a few, such as HINSTANCE, WndProc, and an instance of a class. Perhaps I will put them in a struct to make them less global.
I have a short question: Why not call SetWindowLongPtr right after CreateWindow?
closed account (z05DSL3A)
Why not call SetWindowLongPtr right after CreateWindow?

http://www.cplusplus.com/forum/windows/39141/#msg211114
OK, thanks.
Topic archived. No new replies allowed.