Skip to content

Commit 8c3bdec

Browse files
authored
Introduce malloc module and enhance COM memory management tests. (#899)
* refactor: Revert `test_from_outparam.py` to `test_outparam.py`. * refactor: Eliminate duplicated `IMalloc` definitions and related utilities. * 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. * 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`. * refactor: Centralize `_CoTaskMemFree` import to `malloc.py`. Moves the import of `_CoTaskMemFree` from `GUID.py` to `malloc.py`. * 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. * 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.
1 parent baef773 commit 8c3bdec

File tree

5 files changed

+79
-97
lines changed

5 files changed

+79
-97
lines changed

comtypes/malloc.py

Lines changed: 11 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,9 @@
1-
import logging
2-
from ctypes import (
3-
HRESULT,
4-
POINTER,
5-
OleDLL,
6-
WinDLL,
7-
byref,
8-
c_int,
9-
c_size_t,
10-
c_ulong,
11-
c_void_p,
12-
c_wchar,
13-
c_wchar_p,
14-
cast,
15-
memmove,
16-
sizeof,
17-
wstring_at,
18-
)
1+
from ctypes import HRESULT, POINTER, OleDLL, WinDLL, c_int, c_size_t, c_ulong, c_void_p
192
from ctypes.wintypes import DWORD, LPVOID
3+
from typing import TYPE_CHECKING, Any, Optional
204

215
from comtypes import COMMETHOD, GUID, IUnknown
22-
from comtypes.GUID import _CoTaskMemFree
23-
24-
logger = logging.getLogger(__name__)
6+
from comtypes.GUID import _CoTaskMemFree as _CoTaskMemFree
257

268

279
class IMalloc(IUnknown):
@@ -34,6 +16,14 @@ class IMalloc(IUnknown):
3416
COMMETHOD([], c_int, "DidAlloc", ([], c_void_p, "pv")),
3517
COMMETHOD([], None, "HeapMinimize"), # 25
3618
]
19+
if TYPE_CHECKING:
20+
21+
def Alloc(self, cb: int) -> Optional[int]: ...
22+
def Realloc(self, pv: Any, cb: int) -> Optional[int]: ...
23+
def Free(self, py: Any) -> None: ...
24+
def GetSize(self, pv: Any) -> int: ...
25+
def DidAlloc(self, pv: Any) -> int: ...
26+
def HeapMinimize(self) -> None: ...
3727

3828

3929
_ole32 = OleDLL("ole32")
@@ -48,30 +38,3 @@ class IMalloc(IUnknown):
4838
_CoTaskMemAlloc = _ole32_nohresult.CoTaskMemAlloc
4939
_CoTaskMemAlloc.argtypes = [SIZE_T]
5040
_CoTaskMemAlloc.restype = LPVOID
51-
52-
malloc = POINTER(IMalloc)()
53-
_CoGetMalloc(1, byref(malloc))
54-
assert bool(malloc)
55-
56-
57-
def from_outparam(self):
58-
if not self:
59-
return None
60-
result = wstring_at(self)
61-
# `DidAlloc` method returns;
62-
# * 1 (allocated)
63-
# * 0 (not allocated)
64-
# * -1 (cannot determine or NULL)
65-
# https://learn.microsoft.com/en-us/windows/win32/api/objidl/nf-objidl-imalloc-didalloc
66-
assert malloc.DidAlloc(self), "memory was NOT allocated by CoTaskMemAlloc"
67-
_CoTaskMemFree(self)
68-
return result
69-
70-
71-
def comstring(text, typ=c_wchar_p):
72-
size = (len(text) + 1) * sizeof(c_wchar)
73-
mem = _CoTaskMemAlloc(size)
74-
logger.debug("malloc'd 0x%x, %d bytes" % (mem, size))
75-
ptr = cast(mem, typ)
76-
memmove(mem, text, size)
77-
return ptr

comtypes/test/test_malloc.py

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,68 @@
11
import unittest as ut
2+
from ctypes import HRESULT, POINTER, OleDLL, byref
3+
from ctypes.wintypes import DWORD, HANDLE, LPWSTR
4+
from pathlib import Path
25

3-
from comtypes.malloc import IMalloc # noqa
6+
from comtypes import GUID, hresult
7+
from comtypes.malloc import IMalloc, _CoGetMalloc, _CoTaskMemFree
48

9+
# Constants
10+
# KNOWNFOLDERID
11+
# https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid
12+
FOLDERID_System = GUID("{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}")
13+
# https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/ne-shlobj_core-known_folder_flag
14+
KF_FLAG_DEFAULT = 0x00000000
515

6-
class Test(ut.TestCase): ...
16+
_shell32 = OleDLL("shell32")
17+
_SHGetKnownFolderPath = _shell32.SHGetKnownFolderPath
18+
_SHGetKnownFolderPath.argtypes = [
19+
POINTER(GUID), # rfid
20+
DWORD, # dwFlags
21+
HANDLE, # hToken
22+
POINTER(LPWSTR), # ppszPath
23+
]
24+
_SHGetKnownFolderPath.restype = HRESULT
25+
26+
27+
def _get_malloc() -> IMalloc:
28+
malloc = POINTER(IMalloc)()
29+
_CoGetMalloc(1, byref(malloc))
30+
assert bool(malloc)
31+
return malloc # type: ignore
32+
33+
34+
class Test(ut.TestCase):
35+
def test_Realloc(self):
36+
malloc = _get_malloc()
37+
size1 = 4
38+
ptr1 = malloc.Alloc(size1)
39+
self.assertEqual(malloc.DidAlloc(ptr1), 1)
40+
self.assertEqual(malloc.GetSize(ptr1), size1)
41+
size2 = size1 - 1
42+
ptr2 = malloc.Realloc(ptr1, size2)
43+
self.assertEqual(malloc.DidAlloc(ptr2), 1)
44+
self.assertEqual(malloc.GetSize(ptr2), size2)
45+
size3 = size1 + 1
46+
ptr3 = malloc.Realloc(ptr2, size3)
47+
self.assertEqual(malloc.DidAlloc(ptr3), 1)
48+
self.assertEqual(malloc.GetSize(ptr3), size3)
49+
malloc.Free(ptr3)
50+
self.assertEqual(malloc.DidAlloc(ptr3), 0)
51+
malloc.HeapMinimize()
52+
del ptr3
53+
54+
def test_SHGetKnownFolderPath(self):
55+
ptr = LPWSTR()
56+
hr = _SHGetKnownFolderPath(
57+
byref(FOLDERID_System), KF_FLAG_DEFAULT, None, byref(ptr)
58+
)
59+
self.assertEqual(hr, hresult.S_OK)
60+
self.assertIsInstance(ptr.value, str)
61+
self.assertTrue(Path(ptr.value).exists()) # type: ignore
62+
malloc = _get_malloc()
63+
self.assertEqual(malloc.DidAlloc(ptr), 1)
64+
self.assertGreater(malloc.GetSize(ptr), 0)
65+
_CoTaskMemFree(ptr)
66+
self.assertEqual(malloc.DidAlloc(ptr), 0)
67+
malloc.HeapMinimize()
68+
del ptr
Lines changed: 2 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,13 @@
11
import logging
22
import unittest
3-
from ctypes import (
4-
HRESULT,
5-
POINTER,
6-
OleDLL,
7-
WinDLL,
8-
byref,
9-
c_int,
10-
c_size_t,
11-
c_ulong,
12-
c_void_p,
13-
c_wchar,
14-
c_wchar_p,
15-
cast,
16-
memmove,
17-
sizeof,
18-
wstring_at,
19-
)
20-
from ctypes.wintypes import DWORD, LPVOID
3+
from ctypes import POINTER, byref, c_wchar, c_wchar_p, cast, memmove, sizeof, wstring_at
214
from unittest.mock import patch
225

23-
from comtypes import COMMETHOD, GUID, IUnknown
24-
from comtypes.GUID import _CoTaskMemFree
6+
from comtypes.malloc import IMalloc, _CoGetMalloc, _CoTaskMemAlloc, _CoTaskMemFree
257

268
logger = logging.getLogger(__name__)
279

2810

29-
class IMalloc(IUnknown):
30-
_iid_ = GUID("{00000002-0000-0000-C000-000000000046}")
31-
_methods_ = [
32-
COMMETHOD([], c_void_p, "Alloc", ([], c_ulong, "cb")),
33-
COMMETHOD([], c_void_p, "Realloc", ([], c_void_p, "pv"), ([], c_ulong, "cb")),
34-
COMMETHOD([], None, "Free", ([], c_void_p, "py")),
35-
COMMETHOD([], c_ulong, "GetSize", ([], c_void_p, "pv")),
36-
COMMETHOD([], c_int, "DidAlloc", ([], c_void_p, "pv")),
37-
COMMETHOD([], None, "HeapMinimize"), # 25
38-
]
39-
40-
41-
_ole32 = OleDLL("ole32")
42-
43-
_CoGetMalloc = _ole32.CoGetMalloc
44-
_CoGetMalloc.argtypes = [DWORD, POINTER(POINTER(IMalloc))]
45-
_CoGetMalloc.restype = HRESULT
46-
47-
_ole32_nohresult = WinDLL("ole32")
48-
49-
SIZE_T = c_size_t
50-
_CoTaskMemAlloc = _ole32_nohresult.CoTaskMemAlloc
51-
_CoTaskMemAlloc.argtypes = [SIZE_T]
52-
_CoTaskMemAlloc.restype = LPVOID
53-
5411
malloc = POINTER(IMalloc)()
5512
_CoGetMalloc(1, byref(malloc))
5613
assert bool(malloc)

comtypes/test/test_urlhistory.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from ctypes import *
55

66
from comtypes.client import CreateObject, GetModule
7-
from comtypes.GUID import _CoTaskMemFree
7+
from comtypes.malloc import _CoTaskMemFree
88
from comtypes.patcher import Patch
99

1010
# ./urlhist.tlb was downloaded somewhere from the internet (?)

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ ignore = ["E402"]
8989
"comtypes/test/test_client.py" = ["F401"]
9090
"comtypes/test/test_dict.py" = ["F841"]
9191
"comtypes/test/test_eventinterface.py" = ["F841"]
92-
"comtypes/test/test_from_outparam.py" = ["F841"]
92+
"comtypes/test/test_outparam.py" = ["F841"]
9393
"comtypes/test/test_sapi.py" = ["E401"]
9494
"comtypes/test/test_server.py" = ["F401", "F841"]
9595
"comtypes/test/test_subinterface.py" = ["E401", "F401", "F403", "F405"]

0 commit comments

Comments
 (0)