There are two problems with the Structure
and Union
classes you're using. I'll reproduce a snippet from the code you linked to with one of the classes as example here:
class KEYBDINPUT(ctypes.Structure):
"A particular keyboard event"
_pack_ = 2 # FIXME: don't do this
_fields_ = [
# C:/PROGRA~1/MICROS~4/VC98/Include/winuser.h 4292
('wVk', WORD),
('wScan', WORD),
('dwFlags', DWORD),
('time', DWORD),
('dwExtraInfo', DWORD), # FIXME: use correct data type
]
(The FIXMEs were added by me to point out the problematic parts.)
The first is that you're using _pack_ = 2
, which is incorrect. You should not use any _pack_
alignment specifier at all. The defaults work fine both on 32-bit and on 64-bit Windows.
It's purely by chance that it worked with _pack_ = 2
on 32-bit Windows: All 2-byte sized data types used in the code come in pairs and start on already aligned boundaries and therefore happen to produce the same structures as with 4-byte alignment.
However, under 64-bit Windows the fundamental alignment is 8 byte, so the structures would be misaligned and have wrong sizes if you used _pack_ = 2
or _pack_ = 4
there.
The second problem is that there's no ULONG_PTR
in ctypes.wintypes
, which would be the correct data type for dwExtraInfo
. Under 32-bit Windows ULONG_PTR
is an unsigned 32-bit integer whereas under 64-bit Windows it is an unsigned 64-bit integer.
The MSDN article about Windows Data Types shows how it's defined:
#if defined(_WIN64)
typedef unsigned __int64 ULONG_PTR;
#else
typedef unsigned long ULONG_PTR;
#endif
Using DWORD
or ULONG
for dwExtraInfo
therefore only happens to work under 32-bit Windows and produces the wrong size otherwise.
While using some POINTER
based data type instead would happen to produce a correctly aligned and sized structure, it would be wrong both in meaning as well as in usage, as ULONG_PTR
is "an unsigned integer that can also store a (casted) pointer" rather than an actual pointer.
Looking through wintypes
, you'll notice that WPARAM
happens to be defined exactly like what ULONG_PTR
is supposed to be. So a quick and dirty (but still reasonably robust) way to get ULONG_PTR
would be:
from ctypes.wintypes import WPARAM as ULONG_PTR
Alternatively, you could use a piece of code inspired by theirs and define it yourself:
import ctypes
for ULONG_PTR in [ctypes.c_ulong, ctypes.c_ulonglong]:
if ctypes.sizeof(ULONG_PTR) == ctypes.sizeof(ctypes.c_void_p):
break
else:
raise TypeError("cannot find a suitable type for ULONG_PTR")
Armed with those adaptions, you could define the structures like this:
class MOUSEINPUT(ctypes.Structure):
_fields_ = [
('dw', LONG),
('dy', LONG),
('mouseData', DWORD),
('dwFlags', DWORD),
('time', DWORD),
('dwExtraInfo', ULONG_PTR),
]
class KEYBDINPUT(ctypes.Structure):
_fields_ = [
('wVk', WORD),
('wScan', WORD),
('dwFlags', DWORD),
('time', DWORD),
('dwExtraInfo', ULONG_PTR),
]
class HARDWAREINPUT(ctypes.Structure):
_fields_ = [
('uMsg', DWORD),
('wParamL', WORD),
('wParamH', WORD),
]
class _INPUT(ctypes.Union):
_fields_ = [
('mi', MOUSEINPUT),
('ki', KEYBDINPUT),
('hi', HARDWAREINPUT),
]
class INPUT(ctypes.Structure):
_anonymous_ = ['']
_fields_ = [
('type', DWORD),
('', _INPUT),
]