Python3: implement new way of converting COMError to CallCancelled#9795
Conversation
…r to CallCancelled by hooking WinFunctionType's __call__ in the context of comtypes.
LeonarddeR
left a comment
There was a problem hiding this comment.
Only two things regarding typos in comments.
Also, is all the stuff that's now in comtypesMonkeyPatches still valid, particularly the VARIANT.value.fset monkeypatch? May be it has been implemented in comtypes after we wrote this patch, and it has never been removed?
| import _ctypes | ||
|
|
||
| # A version of ctypes.WINFUNCTYPE | ||
| # that produces a WinFunctionType class whos instance will convert COMError into a CallCancelled exception when called as a function. |
There was a problem hiding this comment.
I'm no native English speaker, but I'm pretty sure whos should be whose.
There was a problem hiding this comment.
I have confirmed we still need all fixes for comtypes, except for the one to support VT_BYREF. However, there code is slightly different to hours, so I am reluctant to remove ours, especially in this pr, without first finding a good example to test with.
From memory the only thing we ever called that took values byref was MS Word's Window.getPoint.
| def new_WINFUNCTYPE(restype,*argtypes,**kwargs): | ||
| cls=old_WINFUNCTYPE(restype,*argtypes,**kwargs) | ||
| class WinFunctionType(cls): | ||
| # We must manually pull the manditory class variables from the super class, |
Link to issue number:
Fixes #9763
Summary of the issue:
When running NVDA from the threshold_py3_staging branch, it fails to start as watchdog cannot replace _ctypes.COMError's constructor with its own custom version in order to raise CallCanceled.
Description of how this pull request fixes the issue:
Rather than raising CallCancelled in COMError's constructor (which is no longer possible):
Wrap the low-level ctypes function call for all comtypes methods and properties, and catch COMError. If the COMError is due to a cancelled COM call, raise CallCancelled. otherwise keep raising the original COMError.
We do this in the following way:
For the duration of importing comtypes for the very first time, we replace ctypes.WINFUNCTTYPE with our own custom version.
As comtypes imports WINFUNCTYPE specifically by name, comtypes will keep our custom version, even after we replace the original after comtypes has been imported.
Comtypes uses our custom version of WINFUNCTYPE in order to generate its ctypes function caller objects, in comtypes._cominterface_meta._make_methods.
Our custom version of WINFUNCTYPE calls the original WINFUNCTYPE, but returns a custom subclass of the resulting function prototype CFuncPtr class.
This custom subclass overrides
__call__and wraps the super call in a try block, catching COMError and raising CallCancelled if the COMError is for a cancelled COM call, otherwise continuing to raise the original COMError.In order to do this, a couple things had to be moved around:
Finally, one extra change was made to baseObject.AutoPropertyObject's property cache getter code:
Previously, when fetching a property, we would try directly fetching it from the property cache, and if it raised KeyError, in the except block we would then actually fetch the property for real.
Although this code still worked in Python3, it produced a rather strange traceback if an exception (E.g. CallCancelled) was raised from inside the property itself. In short Python3 would log the KeyError traceback, and then underneath state that while handling this exception, another exception occured, which was the actual exception in the property.
To make this more readable, the code was slightly restructured to just set a 'missing' variable to True in the except block for KeyError, and then completely outside of the try except blocks, fetch the real property if missing is True. Therefore, if the real property raises an exception, it won't be seen as part of the KeyError exception, which was very missleading.
Testing performed:
in Internet Explorer, ensured that NVDA was able to cancel COM calls, and that CallCancelled was being raised and correctly logged, by placing focus on the "message" button in the test case, opening the Python console, and calling:
Which is known to freeze NVDA's main thread (as that button shows a javascript alert). Then alt+tabbin away from the Python console, which causes NVDA's watchdog to kick in and try cancelling the call.
Once NVDA recovered, looking in the Python Console output, confirmed that the correct CallCancelled traceback was printed.
Known issues with pull request:
None.
Change log entry:
None.