Last active
April 23, 2025 15:58
-
-
Save James-E-A/b98940bc2fc0a03af284717fcc03053f to your computer and use it in GitHub Desktop.
Python wintypes missing GUID structure
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
need to actually test this on ARM or PPC or whatever...