December 12, 2025
nullprogram.com/blog/2025/12/12/
Back in 2017 I wrote about a technique for creating closures in C using JIT-compiled wrapper. It’s neat, though rarely necessary in real programs, so I don’t think about it often. I applied it to qsort , which sadly accepts no context pointer. More practical would be working around insufficient custom allocator interfaces, to create allocation functions at run-time bound to a particular allocation region. I’ve learned a lot since I last wrote about this subject, and a recent article had me thinking about it again, and how I could do better than before. In this article I will enhance Win32 window procedure callbacks with a fifth argument, allowing us to more directly pass extra context. I’m using w64devkit on x64, but the everything here should work out-of-the-box with any x64 toolchain that speaks GNU assembly.
A window procedure has this prototype:
LRESULT Wndproc ( HWND hWnd , UINT Msg , WPARAM wParam , LPARAM lParam , );
To create a window we must first register a class with RegisterClass , which accepts a set of properties describing a window class, including a pointer to one of these functions.
MyState * state = ...; RegisterClassA ( & ( WNDCLASSA ){ // ... . lpfnWndProc = my_wndproc , . lpszClassName = "my_class" , // ... }); HWND hwnd = CreateWindowExA ( "my_class" , ..., state );
The thread drives a message pump with events from the operating system, dispatching them to this procedure, which then manipulates the program state in response:
for ( MSG msg ; GetMessageW ( & msg , 0 , 0 , 0 );) { TranslateMessage ( & msg ); DispatchMessageW ( & msg ); // calls the window procedure }
All four WNDPROC parameters are determined by Win32. There is no context pointer argument. So how does this procedure access the program state? We generally have two options:
... continue reading