Skip to content

Instantly share code, notes, and snippets.

@James-E-A
Last active April 23, 2025 15:58
Show Gist options
  • Save James-E-A/b98940bc2fc0a03af284717fcc03053f to your computer and use it in GitHub Desktop.
Save James-E-A/b98940bc2fc0a03af284717fcc03053f to your computer and use it in GitHub Desktop.
Python wintypes missing GUID structure
import ctypes
from ctypes import GetLastError, WinError, byref, create_unicode_buffer
from uuid import UUID
__all__ = ['wintypes_GUID']
_CLSIDFromString = ctypes.windll.Ole32['CLSIDFromString']
_CLSIDFromString.argtypes = [ctypes.c_wchar_p, ctypes.c_void_p]
_CO_E_CLASSSTRING = ctypes.c_int(0x800401F3).value # nominally positive, but physically negative on most platforms
##_StringFromCLSID = ctypes.windll.Ole32['StringFromCLSID']
##_StringFromCLSID.argtypes = [ctypes.c_void_p, ctypes.POINTER(ctypes.c_wchar_p)]
##_CoTaskMemFree = ctypes.windll.Ole32['CoTaskMemFree']
##_E_OUTOFMEMORY = ctypes.c_int(0x8007000E).value # nominally positive, but physically negative on most platforms
_StringFromGUID2 = ctypes.windll.Ole32['StringFromGUID2']
_StringFromGUID2.argtypes = [ctypes.c_void_p, ctypes.c_wchar_p, ctypes.c_int]
_ERROR_BUFFER_OVERFLOW = ctypes.c_int(0x0000006F).value
class wintypes_GUID(ctypes.Structure):
class __Data4(ctypes.Union):
class __2(ctypes.Structure):
# internal helper class for .__load_from_uuid() and .value getter
_fields_ = [
('_clock_seq_hi_variant', ctypes.c_uint8),
('_clock_seq_low', ctypes.c_uint8),
('_node', ctypes.c_uint8*6),
]
__slots__ = ()
_anonymous_ = ['_2']
_fields_ = [
('Data4', ctypes.c_uint8*8),
('_2', __2)
]
__slots__ = ()
_anonymous_ = ['_Data4']
_fields_ = [
('Data1', ctypes.c_uint32),
('Data2', ctypes.c_uint16),
('Data3', ctypes.c_uint16),
('_Data4', __Data4),
]
__slots__ = ()
def __init__(self, *a, **k):
if len(a) == 1 and isinstance(value := a[0], (str, UUID, int)):
super().__init__(**k)
if isinstance(value, str):
self.__load_from_str(value)
elif isinstance(value, UUID):
self.__load_from_uuid(value)
elif isinstance(value, int):
self.__load_from_uuid(UUID(int=value))
elif __debug__:
raise RuntimeError
else:
super().__init__(*a, **k)
def __repr__(self):
return f"{self.__class__.__name__}({str(self)!r})"
## def __str__(self):
## buf_ptr = ctypes.c_wchar_p()
## try:
## ret = _StringFromCLSID(byref(self), buf_ptr)
##
## if ret != 0:
## # https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-stringfromclsid#return-value
## raise ctypes.WinError(ret)
##
## # ctypes.c_wchar_p.value accessor
## # automatically converts pointed-to NUL-terminated WCHAR string
## # to Python str object, or NULL to None
## assert buf_ptr
## return buf_ptr.value
##
## finally:
## if buf_ptr: _CoTaskMemFree(buf_ptr)
# use StringFromGUID2 instead
# so we don't have to mess around with CoTaskMemFree
# (but leave other implementation commented out, just in case / for future reference(
def __str__(self):
# we need +1 wchar because StringFromGUID2 weirdly does
# null-termination despite *also* doing written-length signalling...
buf = create_unicode_buffer(39)
ret = _StringFromGUID2(byref(self), buf, len(buf))
if __debug__:
# this should *never* occur
if ret == 0:
# https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-stringfromguid2#return-value
raise WinError(_ERROR_BUFFER_OVERFLOW, "The buffer is too small to contain the string.")
# ctypes.Array[ctypes.c_wchar].value accessor
# automatically converts NUL-terminated WCHAR string
# to Python str object
result = buf.value
assert len(result)+1 == ret
return result
def __load_from_str(self, s):
ret = _CLSIDFromString(s, byref(self))
# malformed input *may* occur
if ret != 0:
if ret == _CO_E_CLASSSTRING:
# https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-clsidfromstring#return-value
raise ValueError("The class string was improperly formatted.") from WinError(ret)
else:
raise WinError(ret)
def __load_from_uuid(self, u):
self.Data1 = u.time_low
self.Data2 = u.time_mid
self.Data3 = u.time_hi_version
self._clock_seq_hi_variant = u.clock_seq_hi_variant
self._clock_seq_low = u.clock_seq_low
self._node[:] = int.to_bytes(u.node, 6, 'big')
@property
def value(self):
# adapted from https://github.com/python/cpython/blob/v3.13.3/Lib/uuid.py#L193-L212
return UUID(int=(
(self.Data1 << 96)
| (self.Data2 << 80)
| (self.Data3 << 64)
| (self._clock_seq_hi_variant << 56)
| (self._clock_seq_low << 48)
| int.from_bytes(self._node, 'big')
))
def __bool__(self):
return bool(self.Data1 or self.Data2 or self.Data3 or any(self.Data4))
# accepts string
assert wintypes_GUID('{919108F7-52D1-4320-9BAC-F847DB4148A8}').value\
== UUID('919108f7-52d1-4320-9bac-f847db4148a8')
# accepts UUID
assert wintypes_GUID(UUID('919108f7-52d1-4320-9bac-f847db4148a8')).value\
== UUID('919108f7-52d1-4320-9bac-f847db4148a8')
@James-E-A
Copy link
Author

need to actually test this on ARM or PPC or whatever...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment