Title: Add tests to verify the behavior of basic COM methods. · Issue #126384 · python/cpython · GitHub
Open Graph Title: Add tests to verify the behavior of basic COM methods. · Issue #126384 · python/cpython
X Title: Add tests to verify the behavior of basic COM methods. · Issue #126384 · python/cpython
Description: Feature or enhancement Proposal: I am one of the maintainers of comtypes. comtypes is based on ctypes to implement IUnknown and other COM stuffs. In the past, I reported in gh-124520 that projects dependent on ctypes was broken due to ch...
Open Graph Description: Feature or enhancement Proposal: I am one of the maintainers of comtypes. comtypes is based on ctypes to implement IUnknown and other COM stuffs. In the past, I reported in gh-124520 that projects ...
X Description: Feature or enhancement Proposal: I am one of the maintainers of comtypes. comtypes is based on ctypes to implement IUnknown and other COM stuffs. In the past, I reported in gh-124520 that projects ...
Opengraph URL: https://github.com/python/cpython/issues/126384
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"Add tests to verify the behavior of basic COM methods.","articleBody":"# Feature or enhancement\r\n\r\n### Proposal:\r\n\r\nI am one of the maintainers of [`comtypes`](https://github.com/enthought/comtypes/). `comtypes` is based on `ctypes` to implement [`IUnknown`](https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nn-unknwn-iunknown) and other COM stuffs.\r\n\r\nIn the past, I reported in [gh-124520](https://github.com/python/cpython/issues/124520) that projects dependent on `ctypes` was broken due to changes in Python 3.13.\r\n\r\nI am currently researching whether there are any effective ways to proactively prevent such regressions beyond what I attempted in [gh-125783](https://github.com/python/cpython/issues/125783).\r\n\r\nI noticed that the `cpython` repository might not contain tests for basic COM methods, such as [`QueryInterface`](https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-queryinterface(refiid_void)), [`AddRef`](https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-addref), and [`Release`](https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-release).\r\nThere are many projects besides `comtypes` that define COM interfaces and call COM methods, so I think it’s important to test them.\r\n\r\nI think that simple tests like the one below, which implements a very basic COM interface and calls its methods, might help prevent regressions.\r\n(I removed the complex parts in `comtypes`, such as defining methods, registering pointer types, and using `__del__` to call `Release` through metaclasse magic.)\r\n(I have confirmed that this test passes in a virtual environment with Python 3.11.2, which I could quickly set up.)\r\n\r\n```py\r\nimport sys\r\nimport unittest\r\n\r\n\r\ndef setUpModule():\r\n if sys.platform != \"win32\":\r\n raise unittest.SkipTest(\"Win32 only\")\r\n\r\n\r\nimport ctypes\r\nimport gc\r\nfrom ctypes import HRESULT, POINTER, byref\r\nfrom ctypes.wintypes import BYTE, DWORD, HGLOBAL, WORD\r\n\r\nole32 = ctypes.oledll.ole32\r\noleaut32 = ctypes.oledll.oleaut32\r\n\r\n\r\ndef CLSIDFromString(name):\r\n guid = GUID()\r\n ole32.CLSIDFromString(name, byref(guid))\r\n return guid\r\n\r\n\r\nclass GUID(ctypes.Structure):\r\n _fields_ = [\r\n (\"Data1\", DWORD),\r\n (\"Data2\", WORD),\r\n (\"Data3\", WORD),\r\n (\"Data4\", BYTE * 8),\r\n ]\r\n\r\n\r\nPyInstanceMethod_New = ctypes.pythonapi.PyInstanceMethod_New\r\nPyInstanceMethod_New.argtypes = [ctypes.py_object]\r\nPyInstanceMethod_New.restype = ctypes.py_object\r\nPyInstanceMethod_Type = type(PyInstanceMethod_New(id))\r\n\r\n\r\nclass COM_METHOD:\r\n def __init__(self, index, restype, *argtypes):\r\n self.index = index\r\n self.proto = ctypes.WINFUNCTYPE(restype, *argtypes)\r\n\r\n def __set_name__(self, owner, name):\r\n self.mth = PyInstanceMethod_Type(self.proto(self.index, name))\r\n\r\n def __call__(self, *args, **kwargs):\r\n return self.mth(*args, **kwargs)\r\n\r\n def __get__(self, instance, owner):\r\n if instance is None:\r\n return self\r\n return self.mth.__get__(instance)\r\n\r\n\r\nclass IUnknown(ctypes.c_void_p):\r\n IID = CLSIDFromString(\"{00000000-0000-0000-C000-000000000046}\")\r\n QueryInterface = COM_METHOD(0, HRESULT, POINTER(GUID), POINTER(ctypes.c_void_p))\r\n AddRef = COM_METHOD(1, ctypes.c_long)\r\n Release = COM_METHOD(2, ctypes.c_long)\r\n\r\n\r\nclass ICreateTypeLib(IUnknown):\r\n IID = CLSIDFromString(\"{00020406-0000-0000-C000-000000000046}\")\r\n # `CreateTypeInfo` and more methods should be implemented\r\n\r\n\r\nclass ICreateTypeLib2(ICreateTypeLib):\r\n IID = CLSIDFromString(\"{0002040F-0000-0000-C000-000000000046}\")\r\n # `DeleteTypeInfo` and more methods should be implemented\r\n\r\n\r\nclass ISequentialStream(IUnknown):\r\n IID = CLSIDFromString(\"{0C733A30-2A1C-11CE-ADE5-00AA0044773D}\")\r\n # `Read` and `Write` methods should be implemented\r\n\r\n\r\nclass IStream(ISequentialStream):\r\n IID = CLSIDFromString(\"{0000000C-0000-0000-C000-000000000046}\")\r\n # `Seek` and more methods should be implemented\r\n\r\n\r\nCreateTypeLib2 = oleaut32.CreateTypeLib2\r\nCreateTypeLib2.argtypes = (ctypes.c_int, ctypes.c_wchar_p, POINTER(ICreateTypeLib2))\r\n\r\nCreateStreamOnHGlobal = ole32.CreateStreamOnHGlobal\r\nCreateStreamOnHGlobal.argtypes = (HGLOBAL, ctypes.c_bool, POINTER(IStream))\r\n\r\n\r\nCOINIT_APARTMENTTHREADED = 0x2\r\nS_OK = 0\r\nE_NOINTERFACE = -2147467262\r\n\r\n\r\nclass Test(unittest.TestCase):\r\n def setUp(self):\r\n ole32.CoInitializeEx(None, COINIT_APARTMENTTHREADED)\r\n\r\n def tearDown(self):\r\n ole32.CoUninitialize()\r\n gc.collect()\r\n\r\n def test_create_typelib_2(self):\r\n pctlib = ICreateTypeLib2()\r\n hr = CreateTypeLib2(0, \"sample.tlb\", pctlib)\r\n self.assertEqual(S_OK, hr)\r\n\r\n self.assertEqual(2, pctlib.AddRef())\r\n self.assertEqual(3, pctlib.AddRef())\r\n\r\n self.assertEqual(2, pctlib.Release())\r\n self.assertEqual(1, pctlib.Release())\r\n self.assertEqual(0, pctlib.Release())\r\n\r\n def test_stream(self):\r\n pstm = IStream()\r\n hr = CreateStreamOnHGlobal(None, True, pstm)\r\n self.assertEqual(S_OK, hr)\r\n\r\n self.assertEqual(2, pstm.AddRef())\r\n self.assertEqual(3, pstm.AddRef())\r\n\r\n self.assertEqual(2, pstm.Release())\r\n self.assertEqual(1, pstm.Release())\r\n self.assertEqual(0, pstm.Release())\r\n\r\n def test_query_interface(self):\r\n pctlib2 = ICreateTypeLib2()\r\n CreateTypeLib2(0, \"sample.tlb\", pctlib2)\r\n\r\n pctlib = ICreateTypeLib()\r\n hr1 = pctlib2.QueryInterface(byref(ICreateTypeLib.IID), byref(pctlib))\r\n self.assertEqual(S_OK, hr1)\r\n self.assertEqual(1, pctlib.Release())\r\n\r\n punk = IUnknown()\r\n hr2 = pctlib.QueryInterface(byref(IUnknown.IID), byref(punk))\r\n self.assertEqual(S_OK, hr2)\r\n self.assertEqual(1, punk.Release())\r\n\r\n pstm = IStream()\r\n with self.assertRaises(WindowsError) as e: # Why not `COMError`?\r\n punk.QueryInterface(byref(IStream.IID), byref(pstm))\r\n self.assertEqual(E_NOINTERFACE, e.exception.winerror)\r\n\r\n self.assertEqual(0, punk.Release())\r\n```\r\n\r\n- I am not sure why a `WindowsError` is raised instead of a `COMError` when `QueryInterface` fails.\r\n- Perhaps an interface with even fewer methods should be used in the test.\r\n- At this stage, I think creating custom COM type libraries and interfaces might be excessive.\r\n\r\nI welcome any feedback.\r\n\r\n\r\n\r\n### Has this already been discussed elsewhere?\r\n\r\nNo response given\r\n\r\n### Links to previous discussion of this feature:\r\n\r\n_No response_\n\n\u003c!-- gh-linked-prs --\u003e\n### Linked PRs\n* gh-126610\n* gh-127159\n* gh-127160\n\u003c!-- /gh-linked-prs --\u003e\n","author":{"url":"https://github.com/junkmd","@type":"Person","name":"junkmd"},"datePublished":"2024-11-04T08:13:18.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":2},"url":"https://github.com/126384/cpython/issues/126384"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:c6cd6ada-1542-888f-0e7d-258a44c15180 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | 9F38:16685B:72A132:A27C0E:6A620F15 |
| html-safe-nonce | 220fe841d6e2522eb66aa1c9e757f7009b0bd0a494e5939c75232e8cdf359132 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI5RjM4OjE2Njg1Qjo3MkExMzI6QTI3QzBFOjZBNjIwRjE1IiwidmlzaXRvcl9pZCI6Ijg3NjkyMzEwMDE0NDM1MDM4OTMiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | 775e4c22e89c09b640187e1b122ad3b3c894ae75dbce63374a7861221de53e11 |
| hovercard-subject-tag | issue:2632130207 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/python/cpython/126384/issue_layout |
| twitter:image | https://opengraph.githubassets.com/0035a4cec2477cd36794120ff12514a60bd6f84e4d5e642979b8024d3fc2710c/python/cpython/issues/126384 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/0035a4cec2477cd36794120ff12514a60bd6f84e4d5e642979b8024d3fc2710c/python/cpython/issues/126384 |
| og:image:alt | Feature or enhancement Proposal: I am one of the maintainers of comtypes. comtypes is based on ctypes to implement IUnknown and other COM stuffs. In the past, I reported in gh-124520 that projects ... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | junkmd |
| hostname | github.com |
| expected-hostname | github.com |
| None | be9a4d9589756e22b427637ffbf1123a96e067050e57f5cec403e3080a650ca4 |
| turbo-cache-control | no-preview |
| go-import | github.com/python/cpython git https://github.com/python/cpython.git |
| octolytics-dimension-user_id | 1525981 |
| octolytics-dimension-user_login | python |
| octolytics-dimension-repository_id | 81598961 |
| octolytics-dimension-repository_nwo | python/cpython |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 81598961 |
| octolytics-dimension-repository_network_root_nwo | python/cpython |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 7b5509702f01f876c214a2cbb142690847de0019 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width