From 60ee5ab670373ba96c0b46d4e5cbc5d3968f3c9e Mon Sep 17 00:00:00 2001 From: junkmd Date: Sat, 10 Jan 2026 20:33:32 +0900 Subject: [PATCH 1/7] refactor: Revert `test_from_outparam.py` to `test_outparam.py`. --- comtypes/test/{test_from_outparam.py => test_outparam.py} | 0 pyproject.toml | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename comtypes/test/{test_from_outparam.py => test_outparam.py} (100%) diff --git a/comtypes/test/test_from_outparam.py b/comtypes/test/test_outparam.py similarity index 100% rename from comtypes/test/test_from_outparam.py rename to comtypes/test/test_outparam.py diff --git a/pyproject.toml b/pyproject.toml index b44e4dd3..f9cea138 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -89,7 +89,7 @@ ignore = ["E402"] "comtypes/test/test_client.py" = ["F401"] "comtypes/test/test_dict.py" = ["F841"] "comtypes/test/test_eventinterface.py" = ["F841"] -"comtypes/test/test_from_outparam.py" = ["F841"] +"comtypes/test/test_outparam.py" = ["F841"] "comtypes/test/test_sapi.py" = ["E401"] "comtypes/test/test_server.py" = ["F401", "F841"] "comtypes/test/test_subinterface.py" = ["E401", "F401", "F403", "F405"] From b31d44343ede6d1d8bff3faa56c7ecf1e6ff58da Mon Sep 17 00:00:00 2001 From: junkmd Date: Sat, 10 Jan 2026 20:33:32 +0900 Subject: [PATCH 2/7] refactor: Eliminate duplicated `IMalloc` definitions and related utilities. --- comtypes/malloc.py | 49 +--------------------------------- comtypes/test/test_outparam.py | 46 ++----------------------------- 2 files changed, 3 insertions(+), 92 deletions(-) diff --git a/comtypes/malloc.py b/comtypes/malloc.py index d4543d41..f354df2b 100644 --- a/comtypes/malloc.py +++ b/comtypes/malloc.py @@ -1,27 +1,7 @@ -import logging -from ctypes import ( - HRESULT, - POINTER, - OleDLL, - WinDLL, - byref, - c_int, - c_size_t, - c_ulong, - c_void_p, - c_wchar, - c_wchar_p, - cast, - memmove, - sizeof, - wstring_at, -) +from ctypes import HRESULT, POINTER, OleDLL, WinDLL, c_int, c_size_t, c_ulong, c_void_p from ctypes.wintypes import DWORD, LPVOID from comtypes import COMMETHOD, GUID, IUnknown -from comtypes.GUID import _CoTaskMemFree - -logger = logging.getLogger(__name__) class IMalloc(IUnknown): @@ -48,30 +28,3 @@ class IMalloc(IUnknown): _CoTaskMemAlloc = _ole32_nohresult.CoTaskMemAlloc _CoTaskMemAlloc.argtypes = [SIZE_T] _CoTaskMemAlloc.restype = LPVOID - -malloc = POINTER(IMalloc)() -_CoGetMalloc(1, byref(malloc)) -assert bool(malloc) - - -def from_outparam(self): - if not self: - return None - result = wstring_at(self) - # `DidAlloc` method returns; - # * 1 (allocated) - # * 0 (not allocated) - # * -1 (cannot determine or NULL) - # https://learn.microsoft.com/en-us/windows/win32/api/objidl/nf-objidl-imalloc-didalloc - assert malloc.DidAlloc(self), "memory was NOT allocated by CoTaskMemAlloc" - _CoTaskMemFree(self) - return result - - -def comstring(text, typ=c_wchar_p): - size = (len(text) + 1) * sizeof(c_wchar) - mem = _CoTaskMemAlloc(size) - logger.debug("malloc'd 0x%x, %d bytes" % (mem, size)) - ptr = cast(mem, typ) - memmove(mem, text, size) - return ptr diff --git a/comtypes/test/test_outparam.py b/comtypes/test/test_outparam.py index bd4c05a8..f8d9a8ab 100644 --- a/comtypes/test/test_outparam.py +++ b/comtypes/test/test_outparam.py @@ -1,56 +1,14 @@ import logging import unittest -from ctypes import ( - HRESULT, - POINTER, - OleDLL, - WinDLL, - byref, - c_int, - c_size_t, - c_ulong, - c_void_p, - c_wchar, - c_wchar_p, - cast, - memmove, - sizeof, - wstring_at, -) -from ctypes.wintypes import DWORD, LPVOID +from ctypes import POINTER, byref, c_wchar, c_wchar_p, cast, memmove, sizeof, wstring_at from unittest.mock import patch -from comtypes import COMMETHOD, GUID, IUnknown from comtypes.GUID import _CoTaskMemFree +from comtypes.malloc import IMalloc, _CoGetMalloc, _CoTaskMemAlloc logger = logging.getLogger(__name__) -class IMalloc(IUnknown): - _iid_ = GUID("{00000002-0000-0000-C000-000000000046}") - _methods_ = [ - COMMETHOD([], c_void_p, "Alloc", ([], c_ulong, "cb")), - COMMETHOD([], c_void_p, "Realloc", ([], c_void_p, "pv"), ([], c_ulong, "cb")), - COMMETHOD([], None, "Free", ([], c_void_p, "py")), - COMMETHOD([], c_ulong, "GetSize", ([], c_void_p, "pv")), - COMMETHOD([], c_int, "DidAlloc", ([], c_void_p, "pv")), - COMMETHOD([], None, "HeapMinimize"), # 25 - ] - - -_ole32 = OleDLL("ole32") - -_CoGetMalloc = _ole32.CoGetMalloc -_CoGetMalloc.argtypes = [DWORD, POINTER(POINTER(IMalloc))] -_CoGetMalloc.restype = HRESULT - -_ole32_nohresult = WinDLL("ole32") - -SIZE_T = c_size_t -_CoTaskMemAlloc = _ole32_nohresult.CoTaskMemAlloc -_CoTaskMemAlloc.argtypes = [SIZE_T] -_CoTaskMemAlloc.restype = LPVOID - malloc = POINTER(IMalloc)() _CoGetMalloc(1, byref(malloc)) assert bool(malloc) From ed4a8178155a61adab3a590d02de5690b951895e Mon Sep 17 00:00:00 2001 From: junkmd Date: Sat, 10 Jan 2026 20:33:32 +0900 Subject: [PATCH 3/7] test: Add tests for `IMalloc.Realloc` in `test_malloc.py`. Introduces `comtypes/test/test_malloc.py` to provide test coverage for the `IMalloc.Realloc` method, ensuring proper memory reallocation behavior. --- comtypes/test/test_malloc.py | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/comtypes/test/test_malloc.py b/comtypes/test/test_malloc.py index 597e58b7..c676483e 100644 --- a/comtypes/test/test_malloc.py +++ b/comtypes/test/test_malloc.py @@ -1,6 +1,34 @@ import unittest as ut +from ctypes import POINTER, byref -from comtypes.malloc import IMalloc # noqa +from comtypes.malloc import IMalloc, _CoGetMalloc -class Test(ut.TestCase): ... +def _get_malloc() -> IMalloc: + malloc = POINTER(IMalloc)() + _CoGetMalloc(1, byref(malloc)) + assert bool(malloc) + return malloc # type: ignore + + +class Test(ut.TestCase): + def test_Realloc(self): + malloc = _get_malloc() + size1 = 4 + ptr1 = malloc.Alloc(size1) + self.assertEqual(malloc.DidAlloc(ptr1), 1) + self.assertEqual(malloc.GetSize(ptr1), size1) + size2 = size1 - 1 + ptr2 = malloc.Realloc(ptr1, size2) + self.assertEqual(malloc.DidAlloc(ptr1), 0) + self.assertEqual(malloc.DidAlloc(ptr2), 1) + self.assertEqual(malloc.GetSize(ptr2), size2) + size3 = size1 + 1 + ptr3 = malloc.Realloc(ptr2, size3) + self.assertEqual(malloc.DidAlloc(ptr2), 0) + self.assertEqual(malloc.DidAlloc(ptr3), 1) + self.assertEqual(malloc.GetSize(ptr3), size3) + malloc.Free(ptr3) + self.assertEqual(malloc.DidAlloc(ptr3), 0) + malloc.HeapMinimize() + del ptr3 From c80502302d786f6de5f9b7e01baa3ab6aa140893 Mon Sep 17 00:00:00 2001 From: junkmd Date: Sat, 10 Jan 2026 20:33:32 +0900 Subject: [PATCH 4/7] test: Add `test_SHGetKnownFolderPath` to `test_malloc.py`. Introduces `test_SHGetKnownFolderPath` in `test_malloc.py` to verify the correct functionality of `SHGetKnownFolderPath` and its interaction with memory allocation and deallocation via `IMalloc`. --- comtypes/test/test_malloc.py | 39 +++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/comtypes/test/test_malloc.py b/comtypes/test/test_malloc.py index c676483e..21e79ddb 100644 --- a/comtypes/test/test_malloc.py +++ b/comtypes/test/test_malloc.py @@ -1,8 +1,29 @@ import unittest as ut -from ctypes import POINTER, byref +from ctypes import HRESULT, POINTER, OleDLL, byref +from ctypes.wintypes import DWORD, HANDLE, LPWSTR +from pathlib import Path +from comtypes import GUID, hresult +from comtypes.GUID import _CoTaskMemFree from comtypes.malloc import IMalloc, _CoGetMalloc +# Constants +# KNOWNFOLDERID +# https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid +FOLDERID_System = GUID("{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}") +# https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/ne-shlobj_core-known_folder_flag +KF_FLAG_DEFAULT = 0x00000000 + +_shell32 = OleDLL("shell32") +_SHGetKnownFolderPath = _shell32.SHGetKnownFolderPath +_SHGetKnownFolderPath.argtypes = [ + POINTER(GUID), # rfid + DWORD, # dwFlags + HANDLE, # hToken + POINTER(LPWSTR), # ppszPath +] +_SHGetKnownFolderPath.restype = HRESULT + def _get_malloc() -> IMalloc: malloc = POINTER(IMalloc)() @@ -32,3 +53,19 @@ def test_Realloc(self): self.assertEqual(malloc.DidAlloc(ptr3), 0) malloc.HeapMinimize() del ptr3 + + def test_SHGetKnownFolderPath(self): + ptr = LPWSTR() + hr = _SHGetKnownFolderPath( + byref(FOLDERID_System), KF_FLAG_DEFAULT, None, byref(ptr) + ) + self.assertEqual(hr, hresult.S_OK) + self.assertIsInstance(ptr.value, str) + self.assertTrue(Path(ptr.value).exists()) # type: ignore + malloc = _get_malloc() + self.assertEqual(malloc.DidAlloc(ptr), 1) + self.assertGreater(malloc.GetSize(ptr), 0) + _CoTaskMemFree(ptr) + self.assertEqual(malloc.DidAlloc(ptr), 0) + malloc.HeapMinimize() + del ptr From c29bdd02bf17bbf690f12527dc0d6f6ce2349582 Mon Sep 17 00:00:00 2001 From: junkmd Date: Sat, 10 Jan 2026 20:33:32 +0900 Subject: [PATCH 5/7] refactor: Centralize `_CoTaskMemFree` import to `malloc.py`. Moves the import of `_CoTaskMemFree` from `GUID.py` to `malloc.py`. --- comtypes/malloc.py | 1 + comtypes/test/test_malloc.py | 3 +-- comtypes/test/test_outparam.py | 3 +-- comtypes/test/test_urlhistory.py | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/comtypes/malloc.py b/comtypes/malloc.py index f354df2b..a1061cec 100644 --- a/comtypes/malloc.py +++ b/comtypes/malloc.py @@ -2,6 +2,7 @@ from ctypes.wintypes import DWORD, LPVOID from comtypes import COMMETHOD, GUID, IUnknown +from comtypes.GUID import _CoTaskMemFree as _CoTaskMemFree class IMalloc(IUnknown): diff --git a/comtypes/test/test_malloc.py b/comtypes/test/test_malloc.py index 21e79ddb..69a2bb07 100644 --- a/comtypes/test/test_malloc.py +++ b/comtypes/test/test_malloc.py @@ -4,8 +4,7 @@ from pathlib import Path from comtypes import GUID, hresult -from comtypes.GUID import _CoTaskMemFree -from comtypes.malloc import IMalloc, _CoGetMalloc +from comtypes.malloc import IMalloc, _CoGetMalloc, _CoTaskMemFree # Constants # KNOWNFOLDERID diff --git a/comtypes/test/test_outparam.py b/comtypes/test/test_outparam.py index f8d9a8ab..356b6187 100644 --- a/comtypes/test/test_outparam.py +++ b/comtypes/test/test_outparam.py @@ -3,8 +3,7 @@ from ctypes import POINTER, byref, c_wchar, c_wchar_p, cast, memmove, sizeof, wstring_at from unittest.mock import patch -from comtypes.GUID import _CoTaskMemFree -from comtypes.malloc import IMalloc, _CoGetMalloc, _CoTaskMemAlloc +from comtypes.malloc import IMalloc, _CoGetMalloc, _CoTaskMemAlloc, _CoTaskMemFree logger = logging.getLogger(__name__) diff --git a/comtypes/test/test_urlhistory.py b/comtypes/test/test_urlhistory.py index 62125737..083aa32b 100644 --- a/comtypes/test/test_urlhistory.py +++ b/comtypes/test/test_urlhistory.py @@ -4,7 +4,7 @@ from ctypes import * from comtypes.client import CreateObject, GetModule -from comtypes.GUID import _CoTaskMemFree +from comtypes.malloc import _CoTaskMemFree from comtypes.patcher import Patch # ./urlhist.tlb was downloaded somewhere from the internet (?) From 5fcc908a2dbea6e81fe173d40368466fe7749461 Mon Sep 17 00:00:00 2001 From: junkmd Date: Sat, 10 Jan 2026 20:33:32 +0900 Subject: [PATCH 6/7] feat: Add type hints for `IMalloc` methods in `malloc.py`. Introduces a `TYPE_CHECKING` block within `malloc.py` to provide static type hints for the methods of the `IMalloc` interface. --- comtypes/malloc.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/comtypes/malloc.py b/comtypes/malloc.py index a1061cec..fcde6264 100644 --- a/comtypes/malloc.py +++ b/comtypes/malloc.py @@ -1,5 +1,6 @@ from ctypes import HRESULT, POINTER, OleDLL, WinDLL, c_int, c_size_t, c_ulong, c_void_p from ctypes.wintypes import DWORD, LPVOID +from typing import TYPE_CHECKING, Any, Optional from comtypes import COMMETHOD, GUID, IUnknown from comtypes.GUID import _CoTaskMemFree as _CoTaskMemFree @@ -15,6 +16,14 @@ class IMalloc(IUnknown): COMMETHOD([], c_int, "DidAlloc", ([], c_void_p, "pv")), COMMETHOD([], None, "HeapMinimize"), # 25 ] + if TYPE_CHECKING: + + def Alloc(self, cb: int) -> Optional[int]: ... + def Realloc(self, pv: Any, cb: int) -> Optional[int]: ... + def Free(self, py: Any) -> None: ... + def GetSize(self, pv: Any) -> int: ... + def DidAlloc(self, pv: Any) -> int: ... + def HeapMinimize(self) -> None: ... _ole32 = OleDLL("ole32") From 8830f97809948fc3e3075929871ead954e0018a8 Mon Sep 17 00:00:00 2001 From: junkmd Date: Sat, 10 Jan 2026 20:33:32 +0900 Subject: [PATCH 7/7] fix: Adjust `test_malloc.py` for `IMalloc.Realloc` non-deterministic behavior. `Realloc` can perform in-place re-allocations, which would result in `DidAlloc` returning `1` for the original pointer. --- comtypes/test/test_malloc.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/comtypes/test/test_malloc.py b/comtypes/test/test_malloc.py index 69a2bb07..f3c5fe25 100644 --- a/comtypes/test/test_malloc.py +++ b/comtypes/test/test_malloc.py @@ -40,12 +40,10 @@ def test_Realloc(self): self.assertEqual(malloc.GetSize(ptr1), size1) size2 = size1 - 1 ptr2 = malloc.Realloc(ptr1, size2) - self.assertEqual(malloc.DidAlloc(ptr1), 0) self.assertEqual(malloc.DidAlloc(ptr2), 1) self.assertEqual(malloc.GetSize(ptr2), size2) size3 = size1 + 1 ptr3 = malloc.Realloc(ptr2, size3) - self.assertEqual(malloc.DidAlloc(ptr2), 0) self.assertEqual(malloc.DidAlloc(ptr3), 1) self.assertEqual(malloc.GetSize(ptr3), size3) malloc.Free(ptr3)