From ff0623213a3e042a0bc526cd9463ebcdfc0f8601 Mon Sep 17 00:00:00 2001 From: Jun Komoda <45822440+junkmd@users.noreply.github.com> Date: Sat, 9 Nov 2024 02:49:28 +0000 Subject: [PATCH 01/11] Add tests. --- .../test_win32_com_foreign_func.py | 182 ++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 Lib/test/test_ctypes/test_win32_com_foreign_func.py diff --git a/Lib/test/test_ctypes/test_win32_com_foreign_func.py b/Lib/test/test_ctypes/test_win32_com_foreign_func.py new file mode 100644 index 00000000000000..5c62b443d183a6 --- /dev/null +++ b/Lib/test/test_ctypes/test_win32_com_foreign_func.py @@ -0,0 +1,182 @@ +import sys +import unittest + + +def setUpModule(): + if sys.platform != "win32": + raise unittest.SkipTest("Windows-specific test") + + +import ctypes +import gc +from _ctypes import COMError +from ctypes import HRESULT, POINTER, byref, c_void_p +from ctypes.wintypes import BOOL, BYTE, DWORD, WORD + +ole32 = ctypes.oledll.ole32 + +COINIT_APARTMENTTHREADED = 0x2 +CLSCTX_SERVER = 5 +S_OK = 0 +OUT = 2 +TRUE = 1 +E_NOINTERFACE = -2147467262 + +PyInstanceMethod_New = ctypes.pythonapi.PyInstanceMethod_New +PyInstanceMethod_New.argtypes = [ctypes.py_object] +PyInstanceMethod_New.restype = ctypes.py_object +PyInstanceMethod_Type = type(PyInstanceMethod_New(id)) + + +class GUID(ctypes.Structure): + _fields_ = [ + ("Data1", DWORD), + ("Data2", WORD), + ("Data3", WORD), + ("Data4", BYTE * 8), + ] + + +class ProtoComMethod: + """Utilities for tests that define COM methods.""" + + def __init__(self, index, restype, *argtypes): + self.index = index + self.proto = ctypes.WINFUNCTYPE(restype, *argtypes) + + def __set_name__(self, owner, name): + foreign_func = self.proto(self.index, name, *self.args) + self.mth = PyInstanceMethod_Type(foreign_func) + + def __call__(self, *args): + self.args = args + return self + + def __get__(self, instance, owner=None): + return self.mth.__get__(instance) + + +def CLSIDFromString(name): + guid = GUID() + ole32.CLSIDFromString(name, byref(guid)) + return guid + + +IsEqualGUID = ole32.IsEqualGUID +IsEqualGUID.argtypes = (GUID, GUID) +IsEqualGUID.restype = BOOL + + +IID_IUnknown = CLSIDFromString("{00000000-0000-0000-C000-000000000046}") +IID_IStream = CLSIDFromString("{0000000C-0000-0000-C000-000000000046}") +IID_IPersist = CLSIDFromString("{0000010C-0000-0000-C000-000000000046}") +CLSID_ShellLink = CLSIDFromString("{00021401-0000-0000-C000-000000000046}") + + +proto_qi = ProtoComMethod(0, HRESULT, POINTER(GUID), POINTER(c_void_p)) +proto_addref = ProtoComMethod(1, ctypes.c_long) +proto_release = ProtoComMethod(2, ctypes.c_long) +proto_get_class_id = ProtoComMethod(3, HRESULT, POINTER(GUID)) + + +class ForeignFunctionsThatWillCallComMethodsTests(unittest.TestCase): + def setUp(self): + ole32.CoInitializeEx(None, COINIT_APARTMENTTHREADED) + + def tearDown(self): + ole32.CoUninitialize() + gc.collect() + + @staticmethod + def create_shelllink_persist(typ): + ppst = typ() + ole32.CoCreateInstance( + byref(CLSID_ShellLink), + None, + CLSCTX_SERVER, + byref(IID_IPersist), + byref(ppst), + ) + return ppst + + def test_without_paramflags_and_iid(self): + class IUnknown(c_void_p): + QueryInterface = proto_qi() + AddRef = proto_addref() + Release = proto_release() + + class IPersist(IUnknown): + GetClassID = proto_get_class_id() + + ppst = self.create_shelllink_persist(IPersist) + + clsid = GUID() + hr_getclsid = ppst.GetClassID(byref(clsid)) + self.assertEqual(S_OK, hr_getclsid) + self.assertEqual(TRUE, IsEqualGUID(CLSID_ShellLink, clsid)) + + self.assertEqual(2, ppst.AddRef()) + self.assertEqual(3, ppst.AddRef()) + + punk = IUnknown() + hr_qi = ppst.QueryInterface(IID_IUnknown, punk) + self.assertEqual(S_OK, hr_qi) + self.assertEqual(3, punk.Release()) + + with self.assertRaises(WindowsError) as e: + punk.QueryInterface(IID_IStream, IUnknown()) + self.assertEqual(E_NOINTERFACE, e.exception.winerror) + + self.assertEqual(2, ppst.Release()) + self.assertEqual(1, ppst.Release()) + self.assertEqual(0, ppst.Release()) + + def test_with_paramflags_and_without_iid(self): + class IUnknown(c_void_p): + QueryInterface = proto_qi(None) + AddRef = proto_addref() + Release = proto_release() + + class IPersist(IUnknown): + GetClassID = proto_get_class_id(((OUT, "pClassID"),)) + + ppst = self.create_shelllink_persist(IPersist) + + clsid = ppst.GetClassID() + self.assertEqual(TRUE, IsEqualGUID(CLSID_ShellLink, clsid)) + + punk = IUnknown() + hr_qi = ppst.QueryInterface(IID_IUnknown, punk) + self.assertEqual(S_OK, hr_qi) + self.assertEqual(1, punk.Release()) + + with self.assertRaises(WindowsError) as e: + ppst.QueryInterface(IID_IStream, IUnknown()) + self.assertEqual(E_NOINTERFACE, e.exception.winerror) + + self.assertEqual(0, ppst.Release()) + + def test_with_paramflags_and_iid(self): + class IUnknown(c_void_p): + QueryInterface = proto_qi(None, IID_IUnknown) + AddRef = proto_addref() + Release = proto_release() + + class IPersist(IUnknown): + GetClassID = proto_get_class_id(((OUT, "pClassID"),), IID_IPersist) + + ppst = self.create_shelllink_persist(IPersist) + + clsid = ppst.GetClassID() + self.assertEqual(TRUE, IsEqualGUID(CLSID_ShellLink, clsid)) + + punk = IUnknown() + hr_qi = ppst.QueryInterface(IID_IUnknown, punk) + self.assertEqual(S_OK, hr_qi) + self.assertEqual(1, punk.Release()) + + with self.assertRaises(COMError) as e: + ppst.QueryInterface(IID_IStream, IUnknown()) + self.assertEqual(E_NOINTERFACE, e.exception.hresult) + + self.assertEqual(0, ppst.Release()) From 8049c1a8b5efd052643e35d69567a10ea5105744 Mon Sep 17 00:00:00 2001 From: Jun Komoda <45822440+junkmd@users.noreply.github.com> Date: Sat, 9 Nov 2024 02:50:52 +0000 Subject: [PATCH 02/11] Add comments. --- .../test_ctypes/test_win32_com_foreign_func.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Lib/test/test_ctypes/test_win32_com_foreign_func.py b/Lib/test/test_ctypes/test_win32_com_foreign_func.py index 5c62b443d183a6..05130b0b2ee2db 100644 --- a/Lib/test/test_ctypes/test_win32_com_foreign_func.py +++ b/Lib/test/test_ctypes/test_win32_com_foreign_func.py @@ -48,11 +48,27 @@ def __set_name__(self, owner, name): foreign_func = self.proto(self.index, name, *self.args) self.mth = PyInstanceMethod_Type(foreign_func) + # NOTE: To aid understanding, adding type annotations with `typing` + # would look like this. + # + # _ParamFlags = None | tuple[tuple[int, str] | tuple[int, str, Any], ...] + # + # @overload + # def __call__(self, paramflags: _ParamFlags, iid: GUID, /) -> Self: ... + # @overload + # def __call__(self, paramflags: _ParamFlags, /) -> Self: ... + # @overload + # def __call__(self) -> Self: ... def __call__(self, *args): self.args = args return self def __get__(self, instance, owner=None): + # if instance is None: + # return self + # NOTE: In this test, there is no need to define behavior for the + # custom descriptor as a class attribute, so the above implementation + # is omitted. return self.mth.__get__(instance) From c85bd9362c74a04b0141f8b609c25040331a77fe Mon Sep 17 00:00:00 2001 From: Jun Komoda <45822440+junkmd@users.noreply.github.com> Date: Sat, 9 Nov 2024 03:15:47 +0000 Subject: [PATCH 03/11] Add the platform bridge. --- .../test_win32_com_foreign_func.py | 43 ++++++++----------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/Lib/test/test_ctypes/test_win32_com_foreign_func.py b/Lib/test/test_ctypes/test_win32_com_foreign_func.py index 05130b0b2ee2db..6a8280110e4b7e 100644 --- a/Lib/test/test_ctypes/test_win32_com_foreign_func.py +++ b/Lib/test/test_ctypes/test_win32_com_foreign_func.py @@ -1,20 +1,10 @@ -import sys -import unittest - - -def setUpModule(): - if sys.platform != "win32": - raise unittest.SkipTest("Windows-specific test") - - import ctypes import gc -from _ctypes import COMError -from ctypes import HRESULT, POINTER, byref, c_void_p +import sys +import unittest +from ctypes import POINTER, byref, c_void_p from ctypes.wintypes import BOOL, BYTE, DWORD, WORD -ole32 = ctypes.oledll.ole32 - COINIT_APARTMENTTHREADED = 0x2 CLSCTX_SERVER = 5 S_OK = 0 @@ -78,23 +68,28 @@ def CLSIDFromString(name): return guid -IsEqualGUID = ole32.IsEqualGUID -IsEqualGUID.argtypes = (GUID, GUID) -IsEqualGUID.restype = BOOL +if sys.platform == "win32": + from _ctypes import COMError + from ctypes import HRESULT + ole32 = ctypes.oledll.ole32 -IID_IUnknown = CLSIDFromString("{00000000-0000-0000-C000-000000000046}") -IID_IStream = CLSIDFromString("{0000000C-0000-0000-C000-000000000046}") -IID_IPersist = CLSIDFromString("{0000010C-0000-0000-C000-000000000046}") -CLSID_ShellLink = CLSIDFromString("{00021401-0000-0000-C000-000000000046}") + IsEqualGUID = ole32.IsEqualGUID + IsEqualGUID.argtypes = (GUID, GUID) + IsEqualGUID.restype = BOOL + IID_IUnknown = CLSIDFromString("{00000000-0000-0000-C000-000000000046}") + IID_IStream = CLSIDFromString("{0000000C-0000-0000-C000-000000000046}") + IID_IPersist = CLSIDFromString("{0000010C-0000-0000-C000-000000000046}") + CLSID_ShellLink = CLSIDFromString("{00021401-0000-0000-C000-000000000046}") -proto_qi = ProtoComMethod(0, HRESULT, POINTER(GUID), POINTER(c_void_p)) -proto_addref = ProtoComMethod(1, ctypes.c_long) -proto_release = ProtoComMethod(2, ctypes.c_long) -proto_get_class_id = ProtoComMethod(3, HRESULT, POINTER(GUID)) + proto_qi = ProtoComMethod(0, HRESULT, POINTER(GUID), POINTER(c_void_p)) + proto_addref = ProtoComMethod(1, ctypes.c_long) + proto_release = ProtoComMethod(2, ctypes.c_long) + proto_get_class_id = ProtoComMethod(3, HRESULT, POINTER(GUID)) +@unittest.skipUnless(sys.platform == "win32", "Windows-specific test") class ForeignFunctionsThatWillCallComMethodsTests(unittest.TestCase): def setUp(self): ole32.CoInitializeEx(None, COINIT_APARTMENTTHREADED) From f77b18d67ec6ef9d739277859dd3b0ec321dbb81 Mon Sep 17 00:00:00 2001 From: Jun Komoda <45822440+junkmd@users.noreply.github.com> Date: Sat, 9 Nov 2024 03:33:03 +0000 Subject: [PATCH 04/11] Update `IsEqualGUID`. --- Lib/test/test_ctypes/test_win32_com_foreign_func.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_ctypes/test_win32_com_foreign_func.py b/Lib/test/test_ctypes/test_win32_com_foreign_func.py index 6a8280110e4b7e..ef5b3fc5edf92e 100644 --- a/Lib/test/test_ctypes/test_win32_com_foreign_func.py +++ b/Lib/test/test_ctypes/test_win32_com_foreign_func.py @@ -68,16 +68,16 @@ def CLSIDFromString(name): return guid +def IsEqualGUID(guid1, guid2): + return ole32.IsEqualGUID(byref(guid1), byref(guid2)) + + if sys.platform == "win32": from _ctypes import COMError from ctypes import HRESULT ole32 = ctypes.oledll.ole32 - IsEqualGUID = ole32.IsEqualGUID - IsEqualGUID.argtypes = (GUID, GUID) - IsEqualGUID.restype = BOOL - IID_IUnknown = CLSIDFromString("{00000000-0000-0000-C000-000000000046}") IID_IStream = CLSIDFromString("{0000000C-0000-0000-C000-000000000046}") IID_IPersist = CLSIDFromString("{0000010C-0000-0000-C000-000000000046}") From c43dfa4004f56cc167a1cda6648e3c54a1bc917d Mon Sep 17 00:00:00 2001 From: Jun Komoda <45822440+junkmd@users.noreply.github.com> Date: Sun, 10 Nov 2024 11:36:50 +0000 Subject: [PATCH 05/11] Improve naming. --- .../test_win32_com_foreign_func.py | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/Lib/test/test_ctypes/test_win32_com_foreign_func.py b/Lib/test/test_ctypes/test_win32_com_foreign_func.py index ef5b3fc5edf92e..a302424ff44e6b 100644 --- a/Lib/test/test_ctypes/test_win32_com_foreign_func.py +++ b/Lib/test/test_ctypes/test_win32_com_foreign_func.py @@ -3,7 +3,7 @@ import sys import unittest from ctypes import POINTER, byref, c_void_p -from ctypes.wintypes import BOOL, BYTE, DWORD, WORD +from ctypes.wintypes import BYTE, DWORD, WORD COINIT_APARTMENTTHREADED = 0x2 CLSCTX_SERVER = 5 @@ -62,13 +62,13 @@ def __get__(self, instance, owner=None): return self.mth.__get__(instance) -def CLSIDFromString(name): +def create_guid(name): guid = GUID() ole32.CLSIDFromString(name, byref(guid)) return guid -def IsEqualGUID(guid1, guid2): +def is_equal_guid(guid1, guid2): return ole32.IsEqualGUID(byref(guid1), byref(guid2)) @@ -78,13 +78,15 @@ def IsEqualGUID(guid1, guid2): ole32 = ctypes.oledll.ole32 - IID_IUnknown = CLSIDFromString("{00000000-0000-0000-C000-000000000046}") - IID_IStream = CLSIDFromString("{0000000C-0000-0000-C000-000000000046}") - IID_IPersist = CLSIDFromString("{0000010C-0000-0000-C000-000000000046}") - CLSID_ShellLink = CLSIDFromString("{00021401-0000-0000-C000-000000000046}") + IID_IUnknown = create_guid("{00000000-0000-0000-C000-000000000046}") + IID_IStream = create_guid("{0000000C-0000-0000-C000-000000000046}") + IID_IPersist = create_guid("{0000010C-0000-0000-C000-000000000046}") + CLSID_ShellLink = create_guid("{00021401-0000-0000-C000-000000000046}") - proto_qi = ProtoComMethod(0, HRESULT, POINTER(GUID), POINTER(c_void_p)) - proto_addref = ProtoComMethod(1, ctypes.c_long) + proto_query_interface = ProtoComMethod( + 0, HRESULT, POINTER(GUID), POINTER(c_void_p) + ) + proto_add_ref = ProtoComMethod(1, ctypes.c_long) proto_release = ProtoComMethod(2, ctypes.c_long) proto_get_class_id = ProtoComMethod(3, HRESULT, POINTER(GUID)) @@ -112,8 +114,8 @@ def create_shelllink_persist(typ): def test_without_paramflags_and_iid(self): class IUnknown(c_void_p): - QueryInterface = proto_qi() - AddRef = proto_addref() + QueryInterface = proto_query_interface() + AddRef = proto_add_ref() Release = proto_release() class IPersist(IUnknown): @@ -124,7 +126,7 @@ class IPersist(IUnknown): clsid = GUID() hr_getclsid = ppst.GetClassID(byref(clsid)) self.assertEqual(S_OK, hr_getclsid) - self.assertEqual(TRUE, IsEqualGUID(CLSID_ShellLink, clsid)) + self.assertEqual(TRUE, is_equal_guid(CLSID_ShellLink, clsid)) self.assertEqual(2, ppst.AddRef()) self.assertEqual(3, ppst.AddRef()) @@ -144,8 +146,8 @@ class IPersist(IUnknown): def test_with_paramflags_and_without_iid(self): class IUnknown(c_void_p): - QueryInterface = proto_qi(None) - AddRef = proto_addref() + QueryInterface = proto_query_interface(None) + AddRef = proto_add_ref() Release = proto_release() class IPersist(IUnknown): @@ -154,7 +156,7 @@ class IPersist(IUnknown): ppst = self.create_shelllink_persist(IPersist) clsid = ppst.GetClassID() - self.assertEqual(TRUE, IsEqualGUID(CLSID_ShellLink, clsid)) + self.assertEqual(TRUE, is_equal_guid(CLSID_ShellLink, clsid)) punk = IUnknown() hr_qi = ppst.QueryInterface(IID_IUnknown, punk) @@ -169,8 +171,8 @@ class IPersist(IUnknown): def test_with_paramflags_and_iid(self): class IUnknown(c_void_p): - QueryInterface = proto_qi(None, IID_IUnknown) - AddRef = proto_addref() + QueryInterface = proto_query_interface(None, IID_IUnknown) + AddRef = proto_add_ref() Release = proto_release() class IPersist(IUnknown): @@ -179,7 +181,7 @@ class IPersist(IUnknown): ppst = self.create_shelllink_persist(IPersist) clsid = ppst.GetClassID() - self.assertEqual(TRUE, IsEqualGUID(CLSID_ShellLink, clsid)) + self.assertEqual(TRUE, is_equal_guid(CLSID_ShellLink, clsid)) punk = IUnknown() hr_qi = ppst.QueryInterface(IID_IUnknown, punk) From be31c0638cfb4209e5bf1cf4102d4204f7072f65 Mon Sep 17 00:00:00 2001 From: Jun Komoda <45822440+junkmd@users.noreply.github.com> Date: Sun, 10 Nov 2024 11:48:49 +0000 Subject: [PATCH 06/11] Add comments. --- Lib/test/test_ctypes/test_win32_com_foreign_func.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Lib/test/test_ctypes/test_win32_com_foreign_func.py b/Lib/test/test_ctypes/test_win32_com_foreign_func.py index a302424ff44e6b..cb3367bbc84555 100644 --- a/Lib/test/test_ctypes/test_win32_com_foreign_func.py +++ b/Lib/test/test_ctypes/test_win32_com_foreign_func.py @@ -19,6 +19,7 @@ class GUID(ctypes.Structure): + # https://learn.microsoft.com/en-us/windows/win32/api/guiddef/ns-guiddef-guid _fields_ = [ ("Data1", DWORD), ("Data2", WORD), @@ -64,11 +65,13 @@ def __get__(self, instance, owner=None): def create_guid(name): guid = GUID() + # https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-clsidfromstring ole32.CLSIDFromString(name, byref(guid)) return guid def is_equal_guid(guid1, guid2): + # https://learn.microsoft.com/en-us/windows/win32/api/objbase/nf-objbase-isequalguid return ole32.IsEqualGUID(byref(guid1), byref(guid2)) @@ -83,26 +86,33 @@ def is_equal_guid(guid1, guid2): IID_IPersist = create_guid("{0000010C-0000-0000-C000-000000000046}") CLSID_ShellLink = create_guid("{00021401-0000-0000-C000-000000000046}") + # https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-queryinterface(refiid_void) proto_query_interface = ProtoComMethod( 0, HRESULT, POINTER(GUID), POINTER(c_void_p) ) + # https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-addref proto_add_ref = ProtoComMethod(1, ctypes.c_long) + # https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-release proto_release = ProtoComMethod(2, ctypes.c_long) + # https://learn.microsoft.com/en-us/windows/win32/api/objidl/nf-objidl-ipersist-getclassid proto_get_class_id = ProtoComMethod(3, HRESULT, POINTER(GUID)) @unittest.skipUnless(sys.platform == "win32", "Windows-specific test") class ForeignFunctionsThatWillCallComMethodsTests(unittest.TestCase): def setUp(self): + # https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-coinitializeex ole32.CoInitializeEx(None, COINIT_APARTMENTTHREADED) def tearDown(self): + # https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-couninitialize ole32.CoUninitialize() gc.collect() @staticmethod def create_shelllink_persist(typ): ppst = typ() + # https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-cocreateinstance ole32.CoCreateInstance( byref(CLSID_ShellLink), None, From c09a6a106317d22532b1a5b024b3c01ce86fc1dc Mon Sep 17 00:00:00 2001 From: Jun Komoda <45822440+junkmd@users.noreply.github.com> Date: Sun, 10 Nov 2024 11:53:10 +0000 Subject: [PATCH 07/11] Add comments for typing. --- Lib/test/test_ctypes/test_win32_com_foreign_func.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_ctypes/test_win32_com_foreign_func.py b/Lib/test/test_ctypes/test_win32_com_foreign_func.py index cb3367bbc84555..402645898da307 100644 --- a/Lib/test/test_ctypes/test_win32_com_foreign_func.py +++ b/Lib/test/test_ctypes/test_win32_com_foreign_func.py @@ -42,12 +42,12 @@ def __set_name__(self, owner, name): # NOTE: To aid understanding, adding type annotations with `typing` # would look like this. # - # _ParamFlags = None | tuple[tuple[int, str] | tuple[int, str, Any], ...] + # _ParamFlags = tuple[tuple[int] | tuple[int, str] | tuple[int, str, Any], ...] # # @overload - # def __call__(self, paramflags: _ParamFlags, iid: GUID, /) -> Self: ... + # def __call__(self, paramflags: None | _ParamFlags, iid: GUID, /) -> Self: ... # @overload - # def __call__(self, paramflags: _ParamFlags, /) -> Self: ... + # def __call__(self, paramflags: None | _ParamFlags, /) -> Self: ... # @overload # def __call__(self) -> Self: ... def __call__(self, *args): From f96a1ee8d71e49774806c62de66251b9e16a9834 Mon Sep 17 00:00:00 2001 From: Jun Komoda <45822440+junkmd@users.noreply.github.com> Date: Mon, 18 Nov 2024 14:12:13 +0000 Subject: [PATCH 08/11] Improve platform bridges. --- .../test_win32_com_foreign_func.py | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/Lib/test/test_ctypes/test_win32_com_foreign_func.py b/Lib/test/test_ctypes/test_win32_com_foreign_func.py index 402645898da307..7687e1604871e9 100644 --- a/Lib/test/test_ctypes/test_win32_com_foreign_func.py +++ b/Lib/test/test_ctypes/test_win32_com_foreign_func.py @@ -5,6 +5,14 @@ from ctypes import POINTER, byref, c_void_p from ctypes.wintypes import BYTE, DWORD, WORD +if sys.platform != "win32": + raise unittest.SkipTest("Windows-specific test") + + +from _ctypes import COMError +from ctypes import HRESULT + + COINIT_APARTMENTTHREADED = 0x2 CLSCTX_SERVER = 5 S_OK = 0 @@ -75,30 +83,25 @@ def is_equal_guid(guid1, guid2): return ole32.IsEqualGUID(byref(guid1), byref(guid2)) -if sys.platform == "win32": - from _ctypes import COMError - from ctypes import HRESULT - - ole32 = ctypes.oledll.ole32 +ole32 = ctypes.oledll.ole32 - IID_IUnknown = create_guid("{00000000-0000-0000-C000-000000000046}") - IID_IStream = create_guid("{0000000C-0000-0000-C000-000000000046}") - IID_IPersist = create_guid("{0000010C-0000-0000-C000-000000000046}") - CLSID_ShellLink = create_guid("{00021401-0000-0000-C000-000000000046}") +IID_IUnknown = create_guid("{00000000-0000-0000-C000-000000000046}") +IID_IStream = create_guid("{0000000C-0000-0000-C000-000000000046}") +IID_IPersist = create_guid("{0000010C-0000-0000-C000-000000000046}") +CLSID_ShellLink = create_guid("{00021401-0000-0000-C000-000000000046}") - # https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-queryinterface(refiid_void) - proto_query_interface = ProtoComMethod( - 0, HRESULT, POINTER(GUID), POINTER(c_void_p) - ) - # https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-addref - proto_add_ref = ProtoComMethod(1, ctypes.c_long) - # https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-release - proto_release = ProtoComMethod(2, ctypes.c_long) - # https://learn.microsoft.com/en-us/windows/win32/api/objidl/nf-objidl-ipersist-getclassid - proto_get_class_id = ProtoComMethod(3, HRESULT, POINTER(GUID)) +# https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-queryinterface(refiid_void) +proto_query_interface = ProtoComMethod( + 0, HRESULT, POINTER(GUID), POINTER(c_void_p) +) +# https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-addref +proto_add_ref = ProtoComMethod(1, ctypes.c_long) +# https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-release +proto_release = ProtoComMethod(2, ctypes.c_long) +# https://learn.microsoft.com/en-us/windows/win32/api/objidl/nf-objidl-ipersist-getclassid +proto_get_class_id = ProtoComMethod(3, HRESULT, POINTER(GUID)) -@unittest.skipUnless(sys.platform == "win32", "Windows-specific test") class ForeignFunctionsThatWillCallComMethodsTests(unittest.TestCase): def setUp(self): # https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-coinitializeex From c90ae51d55773b6f1b50708307c9a276fcdd89bd Mon Sep 17 00:00:00 2001 From: Jun Komoda <45822440+junkmd@users.noreply.github.com> Date: Mon, 18 Nov 2024 14:12:13 +0000 Subject: [PATCH 09/11] `ProtoComMethod` -> `create_proto_com_method` --- .../test_win32_com_foreign_func.py | 62 ++++++------------- 1 file changed, 19 insertions(+), 43 deletions(-) diff --git a/Lib/test/test_ctypes/test_win32_com_foreign_func.py b/Lib/test/test_ctypes/test_win32_com_foreign_func.py index 7687e1604871e9..c153697cef05c4 100644 --- a/Lib/test/test_ctypes/test_win32_com_foreign_func.py +++ b/Lib/test/test_ctypes/test_win32_com_foreign_func.py @@ -20,11 +20,6 @@ TRUE = 1 E_NOINTERFACE = -2147467262 -PyInstanceMethod_New = ctypes.pythonapi.PyInstanceMethod_New -PyInstanceMethod_New.argtypes = [ctypes.py_object] -PyInstanceMethod_New.restype = ctypes.py_object -PyInstanceMethod_Type = type(PyInstanceMethod_New(id)) - class GUID(ctypes.Structure): # https://learn.microsoft.com/en-us/windows/win32/api/guiddef/ns-guiddef-guid @@ -36,39 +31,18 @@ class GUID(ctypes.Structure): ] -class ProtoComMethod: - """Utilities for tests that define COM methods.""" - - def __init__(self, index, restype, *argtypes): - self.index = index - self.proto = ctypes.WINFUNCTYPE(restype, *argtypes) - - def __set_name__(self, owner, name): - foreign_func = self.proto(self.index, name, *self.args) - self.mth = PyInstanceMethod_Type(foreign_func) - - # NOTE: To aid understanding, adding type annotations with `typing` - # would look like this. - # - # _ParamFlags = tuple[tuple[int] | tuple[int, str] | tuple[int, str, Any], ...] - # - # @overload - # def __call__(self, paramflags: None | _ParamFlags, iid: GUID, /) -> Self: ... - # @overload - # def __call__(self, paramflags: None | _ParamFlags, /) -> Self: ... - # @overload - # def __call__(self) -> Self: ... - def __call__(self, *args): - self.args = args - return self - - def __get__(self, instance, owner=None): - # if instance is None: - # return self - # NOTE: In this test, there is no need to define behavior for the - # custom descriptor as a class attribute, so the above implementation - # is omitted. - return self.mth.__get__(instance) +def create_proto_com_method(name, index, restype, *argtypes): + proto = ctypes.WINFUNCTYPE(restype, *argtypes) + + def make_method(*args): + foreign_func = proto(index, name, *args) + + def call(self, *args, **kwargs): + return foreign_func(self, *args, **kwargs) + + return call + + return make_method def create_guid(name): @@ -91,15 +65,17 @@ def is_equal_guid(guid1, guid2): CLSID_ShellLink = create_guid("{00021401-0000-0000-C000-000000000046}") # https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-queryinterface(refiid_void) -proto_query_interface = ProtoComMethod( - 0, HRESULT, POINTER(GUID), POINTER(c_void_p) +proto_query_interface = create_proto_com_method( + "QueryInterface", 0, HRESULT, POINTER(GUID), POINTER(c_void_p) ) # https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-addref -proto_add_ref = ProtoComMethod(1, ctypes.c_long) +proto_add_ref = create_proto_com_method("AddRef", 1, ctypes.c_long) # https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-release -proto_release = ProtoComMethod(2, ctypes.c_long) +proto_release = create_proto_com_method("Release", 2, ctypes.c_long) # https://learn.microsoft.com/en-us/windows/win32/api/objidl/nf-objidl-ipersist-getclassid -proto_get_class_id = ProtoComMethod(3, HRESULT, POINTER(GUID)) +proto_get_class_id = create_proto_com_method( + "GetClassID", 3, HRESULT, POINTER(GUID) +) class ForeignFunctionsThatWillCallComMethodsTests(unittest.TestCase): From cc07c448eb9306ed87afb9ad538ba30e35d9839c Mon Sep 17 00:00:00 2001 From: Jun Komoda <45822440+junkmd@users.noreply.github.com> Date: Mon, 18 Nov 2024 14:12:13 +0000 Subject: [PATCH 10/11] `WindowsError` -> `OSError` --- Lib/test/test_ctypes/test_win32_com_foreign_func.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_ctypes/test_win32_com_foreign_func.py b/Lib/test/test_ctypes/test_win32_com_foreign_func.py index c153697cef05c4..819a5bdd3583be 100644 --- a/Lib/test/test_ctypes/test_win32_com_foreign_func.py +++ b/Lib/test/test_ctypes/test_win32_com_foreign_func.py @@ -125,7 +125,7 @@ class IPersist(IUnknown): self.assertEqual(S_OK, hr_qi) self.assertEqual(3, punk.Release()) - with self.assertRaises(WindowsError) as e: + with self.assertRaises(OSError) as e: punk.QueryInterface(IID_IStream, IUnknown()) self.assertEqual(E_NOINTERFACE, e.exception.winerror) @@ -152,7 +152,7 @@ class IPersist(IUnknown): self.assertEqual(S_OK, hr_qi) self.assertEqual(1, punk.Release()) - with self.assertRaises(WindowsError) as e: + with self.assertRaises(OSError) as e: ppst.QueryInterface(IID_IStream, IUnknown()) self.assertEqual(E_NOINTERFACE, e.exception.winerror) From 6c60512c6ee914778b856af7a3451ab58934e5cf Mon Sep 17 00:00:00 2001 From: Jun Komoda <45822440+junkmd@users.noreply.github.com> Date: Thu, 21 Nov 2024 19:47:30 +0900 Subject: [PATCH 11/11] Add `if __name__ == '__main__'` --- Lib/test/test_ctypes/test_win32_com_foreign_func.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Lib/test/test_ctypes/test_win32_com_foreign_func.py b/Lib/test/test_ctypes/test_win32_com_foreign_func.py index 819a5bdd3583be..651c9277d59af9 100644 --- a/Lib/test/test_ctypes/test_win32_com_foreign_func.py +++ b/Lib/test/test_ctypes/test_win32_com_foreign_func.py @@ -182,3 +182,7 @@ class IPersist(IUnknown): self.assertEqual(E_NOINTERFACE, e.exception.hresult) self.assertEqual(0, ppst.Release()) + + +if __name__ == '__main__': + unittest.main()