0

I'm trying to change a Window procedure using Win32 API and ctypes. My code is based to a previous answer that used pywin32 package, with the goal of removing this dependency.

My code raises the following error when I resize the window:

Exception ignored on calling ctypes callback function: <function main.<locals>.new_window_proc at 0x0000028442162170>
Traceback (most recent call last):
  File "test.py", line 40, in new_window_proc
    return user32.CallWindowProcA(old_window_proc, hwnd, msg, wparam, lparam)
ctypes.ArgumentError: argument 5: <class 'OverflowError'>: int too long to convert

Resulting in this :

image

Here is the code :

import pygame
import ctypes
from ctypes import wintypes

user32 = ctypes.windll.user32

WNDPROC = ctypes.WINFUNCTYPE(
    ctypes.c_long, 
    wintypes.HWND, 
    ctypes.c_uint, 
    wintypes.WPARAM,
    wintypes.LPARAM)
WM_SIZE = 0x0005
RDW_INVALIDATE = 0x0001
RDW_ERASE = 0x0004
GWL_WNDPROC = -4


def main():
    pygame.init()

    screen = pygame.display.set_mode((320, 240), pygame.RESIZABLE | pygame.DOUBLEBUF)

    def draw_game():
        screen.fill(pygame.Color('black'))
        pygame.draw.rect(screen, pygame.Color('red'), pygame.Rect(0,0,screen.get_width(),screen.get_height()).inflate(-10, -10))
        pygame.display.flip()
    

    old_window_proc = user32.GetWindowLongPtrA(
        user32.GetForegroundWindow(),
        GWL_WNDPROC
    )

    def new_window_proc(hwnd, msg, wparam, lparam):
        if msg == WM_SIZE:
            draw_game()
            user32.RedrawWindow(hwnd, None, None, RDW_INVALIDATE | RDW_ERASE)
        return user32.CallWindowProcA(old_window_proc, hwnd, msg, wparam, lparam)


    new_window_proc_cb = WNDPROC(new_window_proc)

    user32.SetWindowLongPtrA(
        user32.GetForegroundWindow(), 
        GWL_WNDPROC, 
        ctypes.cast(new_window_proc_cb, ctypes.POINTER(ctypes.c_long))
    )

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                return
            elif event.type == pygame.VIDEORESIZE:
                pygame.display.set_mode((event.w, event.h), pygame.RESIZABLE| pygame.DOUBLEBUF)
        draw_game()
        
if __name__ == '__main__':
    main()

I tried chaning wintypes.WPARAM and wintypes.LPARAM to ctypes.c_ulonglong or ctypes.c_longlong, without success.

Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
Aradyan
  • 5
  • 2

1 Answers1

0

This is a duplicate of [SO]: C function called from Python via ctypes returns incorrect value, but I'm going to post an answer, as things are not that trivial (especially around WNDPROC).

code00.py:

#!/usr/bin/env python

import ctypes as cts
import sys
from ctypes import wintypes as wts

import pygame


if cts.sizeof(cts.c_void_p) >= 8:
    LONG_PTR = cts.c_longlong
else:
    LONG_PTR = cts.c_long

# @TODO - cfati: Verbatim LONG_PTR definition is above, but since working with WNDPROC define it this way.
#  Sizes are the same (separately in 064bit and 032bit environments) - which is the most important thing.
LONG_PTR = wts.LPVOID


WNDPROC = cts.WINFUNCTYPE(
    LONG_PTR,
    wts.HWND,
    wts.UINT,
    wts.WPARAM,
    wts.LPARAM
)

WM_SIZE = 0x0005
RDW_INVALIDATE = 0x0001
RDW_ERASE = 0x0004
GWL_WNDPROC = -4

user32 = cts.windll.user32

GetWindowLongPtrA = user32.GetWindowLongPtrA
GetWindowLongPtrA.argtypes = (wts.HWND, cts.c_int)
GetWindowLongPtrA.restype = LONG_PTR

SetWindowLongPtrA = user32.SetWindowLongPtrA
SetWindowLongPtrA.argtypes = (wts.HWND, cts.c_int, LONG_PTR)
SetWindowLongPtrA.restype = LONG_PTR

GetForegroundWindow = user32.GetForegroundWindow
GetForegroundWindow.argtypes = ()
GetForegroundWindow.restype = wts.HWND

RedrawWindow = user32.RedrawWindow
RedrawWindow.argtypes = (wts.HWND, wts.LPRECT, wts.HRGN, wts.UINT)
RedrawWindow.restype = wts.BOOL

CallWindowProcA = user32.CallWindowProcA
CallWindowProcA.argtypes = (WNDPROC, wts.HWND, wts.UINT, wts.WPARAM, wts.LPARAM)
CallWindowProcA.restype = LONG_PTR

screen = None
old_window_proc = None

def draw_game(screen):
    screen.fill(pygame.Color("black"))
    pygame.draw.rect(screen, pygame.Color("red"), pygame.Rect(0, 0, screen.get_width(), screen.get_height()).inflate(-10, -10))
    pygame.display.flip()


def new_window_proc(hwnd, msg, wparam, lparam):
    global screen
    if msg == WM_SIZE:
        draw_game(screen)
        RedrawWindow(hwnd, None, None, RDW_INVALIDATE | RDW_ERASE)
    global old_window_proc
    if old_window_proc:
        return CallWindowProcA(cts.cast(old_window_proc, WNDPROC), hwnd, msg, wparam, lparam)


new_window_proc_cb = WNDPROC(new_window_proc)


def main(*argv):
    pygame.init()

    global screen
    screen = pygame.display.set_mode((320, 240), pygame.RESIZABLE | pygame.DOUBLEBUF)

    global old_window_proc
    old_window_proc = GetWindowLongPtrA(GetForegroundWindow(), GWL_WNDPROC)

    global new_window_proc_cb

    SetWindowLongPtrA(GetForegroundWindow(), GWL_WNDPROC, new_window_proc_cb)

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                return
            elif event.type == pygame.VIDEORESIZE:
                pygame.display.set_mode((event.w, event.h), pygame.RESIZABLE | pygame.DOUBLEBUF)
        draw_game(screen)


if __name__ == "__main__":
    print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
                                                   64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    rc = main(*sys.argv[1:])
    print("\nDone.\n")
    sys.exit(rc)

Output:

[cfati@CFATI-5510-0:e:\Work\Dev\StackExchange\StackOverflow\q076693194]> "e:\Work\Dev\VEnvs\py_pc064_03.10_test0\Scripts\python.exe" ./code00.py
pygame 2.1.2 (SDL 2.0.18, Python 3.10.9)
Hello from the pygame community. https://www.pygame.org/contribute.html
Python 3.10.9 (tags/v3.10.9:1dd9be6, Dec  6 2022, 20:01:21) [MSC v.1934 64 bit (AMD64)] 064bit on win32


Done.

Notes:

CristiFati
  • 38,250
  • 9
  • 50
  • 87
  • I was able to fix my code by replacing WPARAM and LPARAM by a pointer, ie: `ctypes.POINTER(wintypes.WPARAM)` though it didn't made sense to me, I guess this falls into the "Undefined Behavior" you talked in the linked answer... However I can't understand why you defined `LONG_PTR` as `LPVOID` which is `ctypes.c_void_p` if I look at wintypes file in Github. – Aradyan Jul 19 '23 at 09:12
  • Function pointer can be treated as a *void\**. I used the *WinAPI* specific alias (makes no functional difference). – CristiFati Jul 19 '23 at 09:35