Skip to content

Commit 80ed427

Browse files
authored
Improve error handling for GetEvents with currently unsupported COM servers. (#934)
* test: Expect `KeyError` for MSHTML default source interface Add a test to verify that calling `GetEvents` on an `htmlfile` object raises a `KeyError`. The `IProvideClassInfo2` for this object returns a null GUID for its default source interface. This test confirms that `comtypes` correctly fails when it cannot find this null interface in the registry. * feat: Handle `GUID_NULL` for outgoing interface IID in `FindOutgoingInterface`. When `IProvideClassInfo2` returns `GUID_NULL` as the outgoing interface IID, `FindOutgoingInterface` now raises a `NotImplementedError`. This addresses cases where some COM servers (like `htmlfile`) return a null GUID instead of the default source interface's GUID. A new test case `test_retrieved_outgoing_iid_is_guid_null` has been added to `test_eventinterface.py` to verify this behavior. * docs: Add comment on `GUID_NULL` from `IProvideClassInfo2` in `FindOutgoingInterface`. Adds a comment to `FindOutgoingInterface` to explain that some COM servers may return `GUID_NULL` from `IProvideClassInfo2.GetGUID` instead of a valid interface IID. This clarifies the subsequent error handling for this specific edge case. * test: Expect `AssertionError` for IMAPI2 custom event interface. Add `Test_IMAPI2FS` to `test_eventinterface.py` to verify that `GetEvents` raises an `AssertionError` when used with the `MsftFileSystemImage` object. The default event interface `DFileSystemImageEvents` is a custom `TKIND_INTERFACE` that inherits from `IDispatch`, but is neither a dual nor pure dispatch interface. Its v-table methods, as generated from type info, lack the `dispid` attributes that `GetEvents` requires. This test confirms the expected failure. * feat: Raise `NotImplementedError` for event interfaces lacking DISPIDs. Replace `assert` in `CreateEventReceiver` with an explicit check for `dispid`, raising `NotImplementedError` with a message when DISPIDs are missing. * test: Rename `Test_IMAPI2FS` test and clarify DISPID requirement. Rename `Test_IMAPI2FS.test` to `test_event_methods_lack_dispids` to better reflect the cause of the `NotImplementedError`.
1 parent 5c416b8 commit 80ed427

2 files changed

Lines changed: 53 additions & 1 deletion

File tree

comtypes/client/_events.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,10 @@ def FindOutgoingInterface(source: IUnknown) -> type[IUnknown]:
120120
except COMError:
121121
pass
122122
else:
123+
if guid == comtypes.GUID():
124+
# Some COM servers, even if they implement `IProvideClassInfo2`,
125+
# may return GUID_NULL instead of the default source interface's GUID.
126+
raise NotImplementedError("retrieved outgoing interface IID is GUID_NULL")
123127
# another try: block needed?
124128
try:
125129
interface = comtypes.com_interface_registry[str(guid)]
@@ -258,7 +262,15 @@ def _get_method_finder_(self, itf: type[IUnknown]) -> _MethodFinder:
258262
# Can dispid be at a different index? Should check code generator...
259263
# ...but hand-written code should also work...
260264
dispid = m.idlflags[0]
261-
assert isinstance(dispid, comtypes.dispid)
265+
if not isinstance(dispid, comtypes.dispid):
266+
# The interface is a subclass of `IDispatch` but its methods do not
267+
# have DISPIDs, indicating it's not an interface suitable for event
268+
# handling.
269+
raise NotImplementedError(
270+
"Event receiver creation requires event methods to have DISPIDs "
271+
f"for dispatching, but '{interface.__name__}' ({interface._iid_}) "
272+
"lacks them, even though it inherits from 'IDispatch'."
273+
)
262274
impl = finder.get_impl(interface, m.name, m.paramflags, m.idlflags)
263275
# XXX Wouldn't work for 'propget', 'propput', 'propputref'
264276
# methods - are they allowed on event interfaces?

comtypes/test/test_eventinterface.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import gc
2+
import tempfile
23
import time
34
import unittest as ut
45
from ctypes import HRESULT, byref
56
from ctypes.wintypes import MSG
7+
from pathlib import Path
68

79
from comtypes import COMMETHOD, GUID, IUnknown
810
from comtypes.automation import DISPID
@@ -111,5 +113,43 @@ def test_nondefault_eventinterface(self):
111113
del conn
112114

113115

116+
class Test_MSHTML(ut.TestCase):
117+
def test_retrieved_outgoing_iid_is_guid_null(self):
118+
doc = CreateObject("htmlfile")
119+
sink = object()
120+
# MSHTML's HTMLDocument (which is what `CreateObject('htmlfile')`
121+
# returns) does not expose a valid default source interface through
122+
# `IProvideClassInfo2`.
123+
with self.assertRaises(NotImplementedError):
124+
GetEvents(doc, sink)
125+
126+
127+
class Test_IMAPI2FS(ut.TestCase):
128+
def setUp(self):
129+
CLSID_MsftFileSystemImage = GUID("{2C941FC5-975B-59BE-A960-9A2A262853A5}")
130+
self.image = CreateObject(CLSID_MsftFileSystemImage)
131+
self.image.FileSystemsToCreate = 1 # FsiFileSystemISO9660
132+
td = tempfile.TemporaryDirectory()
133+
self.tmp_dir = Path(td.name)
134+
self.addCleanup(td.cleanup)
135+
136+
def tearDown(self):
137+
del self.image
138+
# Force garbage collection and wait slightly to ensure COM resources
139+
# are released properly between tests.
140+
gc.collect()
141+
time.sleep(2)
142+
143+
def test_event_methods_lack_dispids(self):
144+
sink = object()
145+
# The default event interface for IMAPI2's FileSystemImage is
146+
# `DFileSystemImageEvents`. Although it inherits from `IDispatch`,
147+
# it is a custom interface (`TKIND_INTERFACE`), not a dual or pure
148+
# dispatch interface (`TKIND_DISPATCH`); therefore, its methods
149+
# do not have DISPIDs.
150+
with self.assertRaises(NotImplementedError):
151+
GetEvents(self.image, sink)
152+
153+
114154
if __name__ == "__main__":
115155
ut.main()

0 commit comments

Comments
 (0)