diff --git a/comtypes/client/_events.py b/comtypes/client/_events.py index e6b662df..603c5531 100644 --- a/comtypes/client/_events.py +++ b/comtypes/client/_events.py @@ -120,6 +120,10 @@ def FindOutgoingInterface(source: IUnknown) -> type[IUnknown]: except COMError: pass else: + if guid == comtypes.GUID(): + # Some COM servers, even if they implement `IProvideClassInfo2`, + # may return GUID_NULL instead of the default source interface's GUID. + raise NotImplementedError("retrieved outgoing interface IID is GUID_NULL") # another try: block needed? try: interface = comtypes.com_interface_registry[str(guid)] @@ -258,7 +262,15 @@ def _get_method_finder_(self, itf: type[IUnknown]) -> _MethodFinder: # Can dispid be at a different index? Should check code generator... # ...but hand-written code should also work... dispid = m.idlflags[0] - assert isinstance(dispid, comtypes.dispid) + if not isinstance(dispid, comtypes.dispid): + # The interface is a subclass of `IDispatch` but its methods do not + # have DISPIDs, indicating it's not an interface suitable for event + # handling. + raise NotImplementedError( + "Event receiver creation requires event methods to have DISPIDs " + f"for dispatching, but '{interface.__name__}' ({interface._iid_}) " + "lacks them, even though it inherits from 'IDispatch'." + ) impl = finder.get_impl(interface, m.name, m.paramflags, m.idlflags) # XXX Wouldn't work for 'propget', 'propput', 'propputref' # methods - are they allowed on event interfaces? diff --git a/comtypes/test/test_eventinterface.py b/comtypes/test/test_eventinterface.py index d30c0339..9fffc1a9 100644 --- a/comtypes/test/test_eventinterface.py +++ b/comtypes/test/test_eventinterface.py @@ -1,8 +1,10 @@ import gc +import tempfile import time import unittest as ut from ctypes import HRESULT, byref from ctypes.wintypes import MSG +from pathlib import Path from comtypes import COMMETHOD, GUID, IUnknown from comtypes.automation import DISPID @@ -111,5 +113,43 @@ def test_nondefault_eventinterface(self): del conn +class Test_MSHTML(ut.TestCase): + def test_retrieved_outgoing_iid_is_guid_null(self): + doc = CreateObject("htmlfile") + sink = object() + # MSHTML's HTMLDocument (which is what `CreateObject('htmlfile')` + # returns) does not expose a valid default source interface through + # `IProvideClassInfo2`. + with self.assertRaises(NotImplementedError): + GetEvents(doc, sink) + + +class Test_IMAPI2FS(ut.TestCase): + def setUp(self): + CLSID_MsftFileSystemImage = GUID("{2C941FC5-975B-59BE-A960-9A2A262853A5}") + self.image = CreateObject(CLSID_MsftFileSystemImage) + self.image.FileSystemsToCreate = 1 # FsiFileSystemISO9660 + td = tempfile.TemporaryDirectory() + self.tmp_dir = Path(td.name) + self.addCleanup(td.cleanup) + + def tearDown(self): + del self.image + # Force garbage collection and wait slightly to ensure COM resources + # are released properly between tests. + gc.collect() + time.sleep(2) + + def test_event_methods_lack_dispids(self): + sink = object() + # The default event interface for IMAPI2's FileSystemImage is + # `DFileSystemImageEvents`. Although it inherits from `IDispatch`, + # it is a custom interface (`TKIND_INTERFACE`), not a dual or pure + # dispatch interface (`TKIND_DISPATCH`); therefore, its methods + # do not have DISPIDs. + with self.assertRaises(NotImplementedError): + GetEvents(self.image, sink) + + if __name__ == "__main__": ut.main()