From e0724562cafcbb79a8bcb319a4d8a2b02bb9fe2d Mon Sep 17 00:00:00 2001 From: junkmd Date: Fri, 13 Feb 2026 21:52:54 +0900 Subject: [PATCH 1/5] refactor: Define `ITEMIDLIST` and `SHITEMID` as `Structure` in `shelllink.py`. - Replace the placeholder `ITEMIDLIST = c_int` with actual `Structure` definitions to match Windows API specifications. - Include `SHITEMID` with `cb` and `abID` fields. --- comtypes/shelllink.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/comtypes/shelllink.py b/comtypes/shelllink.py index 022604d8..dedc4748 100644 --- a/comtypes/shelllink.py +++ b/comtypes/shelllink.py @@ -1,5 +1,6 @@ from ctypes import ( POINTER, + Structure, byref, c_char_p, c_int, @@ -8,7 +9,14 @@ create_string_buffer, create_unicode_buffer, ) -from ctypes.wintypes import DWORD, MAX_PATH, WIN32_FIND_DATAA, WIN32_FIND_DATAW +from ctypes.wintypes import ( + BYTE, + DWORD, + MAX_PATH, + USHORT, + WIN32_FIND_DATAA, + WIN32_FIND_DATAW, +) from typing import TYPE_CHECKING, Literal from comtypes import COMMETHOD, GUID, HRESULT, CoClass, IUnknown @@ -42,8 +50,15 @@ HOTKEYF_EXT = 0x08 HOTKEYF_SHIFT = 0x01 -# fake these... -ITEMIDLIST = c_int + +class SHITEMID(Structure): + _fields_ = [("cb", USHORT), ("abID", BYTE * 1)] + + +class ITEMIDLIST(Structure): + _fields_ = [("mkid", SHITEMID)] + + LPITEMIDLIST = LPCITEMIDLIST = POINTER(ITEMIDLIST) From 01b4aa0396894f75a91a3a9fd9734a9e270bd512 Mon Sep 17 00:00:00 2001 From: junkmd Date: Fri, 13 Feb 2026 21:52:54 +0900 Subject: [PATCH 2/5] test: Add `IShellLink::SetIDList` and `GetIDList` tests. - Add `test_set_and_get_idlist` to `Test_IShellLinkA` and `Test_IShellLinkW` in `test_shelllink.py`. - Verify setting and retrieving a manually constructed `ITEMIDLIST`. - Use `_CoTaskMemFree` to release the pointer returned by `GetIDList`. --- comtypes/test/test_shelllink.py | 56 +++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/comtypes/test/test_shelllink.py b/comtypes/test/test_shelllink.py index 112801a5..3967f566 100644 --- a/comtypes/test/test_shelllink.py +++ b/comtypes/test/test_shelllink.py @@ -1,9 +1,12 @@ +import struct import tempfile import unittest as ut +from ctypes import addressof, cast, create_string_buffer, string_at from pathlib import Path import comtypes.hresult from comtypes import GUID, CoCreateInstance, shelllink +from comtypes.malloc import _CoTaskMemFree from comtypes.persist import IPersistFile CLSID_ShellLink = GUID("{00021401-0000-0000-C000-000000000046}") @@ -66,6 +69,32 @@ def test_set_and_get_icon_location(self): self.assertEqual(icon_path, str(self.src_file).encode("utf-8")) self.assertEqual(index, 1) + def test_set_and_get_idlist(self): + # Create a manual PIDL for testing. + # In reality, the `abID` portion contains Shell namespace identifiers. + # (e.g. file system item IDs, special folder tokens, virtual folder + # GUIDs, etc.) + # These IDs are referenced/used by Shell folders to identify and locate + # specific items in the namespace. + data = b"\xde\xad\xbe\xef" # dummy test data (meaningless in real use). + cb = len(data) + 2 + # ITEMIDLIST format: + # - little-endian ('<') + # - cb as 16-bit unsigned integer ('H') + # - data bytes of length ('{len(data)}s') + # - terminator as 16-bit unsigned integer ('H') + raw_pidl = struct.pack(f" Date: Fri, 13 Feb 2026 21:52:54 +0900 Subject: [PATCH 3/5] refactor: Improve `IShellLink` type hints for `GetIDList` and `SetIDList`. - Use `_Pointer[ITEMIDLIST]` for `ITEMIDLIST` pointers in `IShellLinkA` and `IShellLinkW`. - Specify `hints.Hresult` as the return type for `SetIDList`. - Import `_Pointer` from `ctypes` within the `TYPE_CHECKING` block. --- comtypes/shelllink.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/comtypes/shelllink.py b/comtypes/shelllink.py index dedc4748..901e81a5 100644 --- a/comtypes/shelllink.py +++ b/comtypes/shelllink.py @@ -22,6 +22,8 @@ from comtypes import COMMETHOD, GUID, HRESULT, CoClass, IUnknown if TYPE_CHECKING: + from ctypes import _Pointer + from comtypes import hints # type: ignore @@ -149,8 +151,8 @@ class IShellLinkA(IUnknown): if TYPE_CHECKING: - def GetIDList(self) -> hints.Incomplete: ... - def SetIDList(self, pidl: hints.Incomplete) -> hints.Incomplete: ... + def GetIDList(self) -> _Pointer[ITEMIDLIST]: ... + def SetIDList(self, pidl: _Pointer[ITEMIDLIST]) -> hints.Hresult: ... def SetDescription(self, pszName: bytes) -> hints.Incomplete: ... def SetWorkingDirectory(self, pszDir: bytes) -> hints.Hresult: ... def SetArguments(self, pszArgs: bytes) -> hints.Hresult: ... @@ -284,8 +286,8 @@ class IShellLinkW(IUnknown): if TYPE_CHECKING: - def GetIDList(self) -> hints.Incomplete: ... - def SetIDList(self, pidl: hints.Incomplete) -> hints.Incomplete: ... + def GetIDList(self) -> _Pointer[ITEMIDLIST]: ... + def SetIDList(self, pidl: _Pointer[ITEMIDLIST]) -> hints.Hresult: ... def SetDescription(self, pszName: str) -> hints.Incomplete: ... def SetWorkingDirectory(self, pszDir: str) -> hints.Hresult: ... def SetArguments(self, pszArgs: str) -> hints.Hresult: ... From d94e35a166230478be388106377baebc3c7b7fd7 Mon Sep 17 00:00:00 2001 From: junkmd Date: Fri, 13 Feb 2026 21:52:54 +0900 Subject: [PATCH 4/5] test: Use `ILIsEqual` for robust `ITEMIDLIST` comparisons in `test_shelllink.py`. - Add assertions using `ILIsEqual` in `Test_IShellLinkA` and `Test_IShellLinkW` to ensure correct `ITEMIDLIST` handling. --- comtypes/test/test_shelllink.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/comtypes/test/test_shelllink.py b/comtypes/test/test_shelllink.py index 3967f566..de40c079 100644 --- a/comtypes/test/test_shelllink.py +++ b/comtypes/test/test_shelllink.py @@ -1,16 +1,25 @@ import struct import tempfile import unittest as ut -from ctypes import addressof, cast, create_string_buffer, string_at +from ctypes import WinDLL, addressof, cast, create_string_buffer, string_at +from ctypes.wintypes import BOOL from pathlib import Path import comtypes.hresult from comtypes import GUID, CoCreateInstance, shelllink from comtypes.malloc import _CoTaskMemFree from comtypes.persist import IPersistFile +from comtypes.shelllink import LPITEMIDLIST as PIDLIST_ABSOLUTE CLSID_ShellLink = GUID("{00021401-0000-0000-C000-000000000046}") +_shell32 = WinDLL("shell32") + +# https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-ilisequal +_ILIsEqual = _shell32.ILIsEqual +_ILIsEqual.argtypes = [PIDLIST_ABSOLUTE, PIDLIST_ABSOLUTE] +_ILIsEqual.restype = BOOL + class Test_IShellLinkA(ut.TestCase): def setUp(self): @@ -93,6 +102,7 @@ def test_set_and_get_idlist(self): self.assertEqual(idlist.mkid.cb, cb) # Access the raw data from the pointer. self.assertEqual(string_at(addressof(idlist.mkid.abID), len(data)), data) + self.assertTrue(_ILIsEqual(in_pidl, out_pidl)) _CoTaskMemFree(out_pidl) @@ -181,4 +191,5 @@ def test_set_and_get_idlist(self): self.assertEqual(idlist.mkid.cb, cb) # Access the raw data from the pointer. self.assertEqual(string_at(addressof(idlist.mkid.abID), len(data)), data) + self.assertTrue(_ILIsEqual(in_pidl, out_pidl)) _CoTaskMemFree(out_pidl) From fa8a55589a055cd6902a74e29cf0d5101b8b5864 Mon Sep 17 00:00:00 2001 From: junkmd Date: Fri, 13 Feb 2026 21:52:54 +0900 Subject: [PATCH 5/5] test: Verify `PIDL` memory allocation using `CoGetMalloc().DidAlloc` in `test_shelllink.py`. - Assert that input `PIDL`s are not COM-allocated and output `PIDL`s are, ensuring proper `CoTaskMemFree` usage. --- comtypes/test/test_shelllink.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/comtypes/test/test_shelllink.py b/comtypes/test/test_shelllink.py index de40c079..2726e808 100644 --- a/comtypes/test/test_shelllink.py +++ b/comtypes/test/test_shelllink.py @@ -7,7 +7,7 @@ import comtypes.hresult from comtypes import GUID, CoCreateInstance, shelllink -from comtypes.malloc import _CoTaskMemFree +from comtypes.malloc import CoGetMalloc, _CoTaskMemFree from comtypes.persist import IPersistFile from comtypes.shelllink import LPITEMIDLIST as PIDLIST_ABSOLUTE @@ -103,6 +103,12 @@ def test_set_and_get_idlist(self): # Access the raw data from the pointer. self.assertEqual(string_at(addressof(idlist.mkid.abID), len(data)), data) self.assertTrue(_ILIsEqual(in_pidl, out_pidl)) + malloc = CoGetMalloc() + # Verify that the input PIDL is not COM-allocated memory. + self.assertFalse(malloc.DidAlloc(in_pidl)) + # Verify that the output PIDL IS COM-allocated memory, requiring + # `CoTaskMemFree` for proper deallocation. + self.assertTrue(malloc.DidAlloc(out_pidl)) _CoTaskMemFree(out_pidl) @@ -192,4 +198,10 @@ def test_set_and_get_idlist(self): # Access the raw data from the pointer. self.assertEqual(string_at(addressof(idlist.mkid.abID), len(data)), data) self.assertTrue(_ILIsEqual(in_pidl, out_pidl)) + malloc = CoGetMalloc() + # Verify that the input PIDL is not COM-allocated memory. + self.assertFalse(malloc.DidAlloc(in_pidl)) + # Verify that the output PIDL IS COM-allocated memory, requiring + # `CoTaskMemFree` for proper deallocation. + self.assertTrue(malloc.DidAlloc(out_pidl)) _CoTaskMemFree(out_pidl)