I have been writing quite a lot of Python using the gateway class pattern. Python does COM inter-operability well including throwing errors with rich error info. However, do remember to provide an error handler or you get a strange error message.
Background: Error Handling in COM
I sketch the briefest of details below on HRESULT and ICreateErrorInfo but for a fuller read see this link.
HRESULT
Consider the following extract from the IDL of the Microsoft Scripting Runtime, specifically the Dictionary's RemoveAll method. (There is no return value to confuse matters). Understand that the HRESULT signals the success or failure of the method invocation. An HRESULT is a 32-bit integer, if it is zero (symbollically S_OK) then the method succeeded but any other value is an error, so 32-bits can support billions or error numbers. You can see how this 'error space' is divided at this link.
interface IDictionary : IDispatch {
[id(0x00000008), helpstring("Remove all information from the dictionary."), helpcontext(0x00214b41)]
HRESULT RemoveAll();
ICreateErrorInfo interface
Nevertheless, VBA programmers are used to error description strings and the HRESULT does not carry this information. Instead, an ErrorInfo object is created by calling the ICreateErrorInfo there you can see the error Description being settable. Python supports rich error handling via ICreateErrorInfo.
Code
So some code we illustrate..
Python Class
So the following Python code is a class with two methods, the first method PythonThrowsAnEror results in Python throwing an error because we try to call a method that does not exist. In the second method, we throw a custom error; this could be a business logic error as well as a system error.
class ThrowsErrors(object):
_reg_clsid_ = "{FD538AF6-6B9C-4E53-8013-93D74665F23E}"
_reg_progid_ = 'PythonComTypes.ThrowsErrors'
_public_methods_ = ['PythonThrowsAnEror','ThrowMyOwnError']
def PythonThrowsAnEror(self):
a=self.noexist()
def ThrowMyOwnError(self):
a=2+2
raise COMException(description="Throw an error!", scode=winerror.E_FAIL, source = "ThrowsErrors")
if __name__ == '__main__':
print("Registering COM servers...")
import win32com.server.register
win32com.server.register.UseCommandLine(ThrowsErrors)
VBA Test Client code
So here is some test code but you must run the above Python class first to register it!
Sub TestThrowsErrors()
On Error GoTo PythonErrHand
Dim obj As Object
Set obj = VBA.CreateObject("PythonComTypes.ThrowsErrors")
Debug.Print obj.PythonThrowsAnEror
'Debug.Print obj.ThrowMyOwnError
SingleExit:
Exit Sub
PythonErrHand:
Debug.Print err.Description, Hex$(err.Number), err.source
End Sub
Running the code prints the following (edited) in the Immediate window...
Unexpected Python Error: Traceback (most recent call last):
File "C:\PROGRA~2\MICROS~4\Shared\PYTHON~1\lib\site-packages\win32com\server\policy.py", line 278, in _Invoke_
return self._invoke_(dispid, lcid, wFlags, args)
File "C:\PROGRA~2\MICROS~4\Shared\PYTHON~1\lib\site-packages\win32com\server\policy.py", line 283, in _invoke_
return S_OK, -1, self._invokeex_(dispid, lcid, wFlags, args, None, None)
File "C:\PROGRA~2\MICROS~4\Shared\PYTHON~1\lib\site-packages\win32com\server\policy.py", line 586, in _invokeex_
return func(*args)
File "N:\source\repos\ThrowsErrors\ThrowsErrors\ThrowsErrors.py", line 10, in PythonThrowsAnEror
a=self.noexist()
AttributeError: 'ThrowsErrors' object has no attribute 'noexist'
80004005 Python COM Server Internal Error
You can see that Python is passing a whole error stack via the Err.Description field. For the error number it is using &H80004005 which is E_FAIL which signifies a general error.
If &H80004005 is the favoured catch all error number for errors then we can do the same for our custom errors. In the Python code above one can see in the ThrowMyOwnError() method we also use E_FAIL. If you uncomment the second method call (and comment the first to suppress it) then the test code now prints
Throw an error! 80004005 ThrowsErrors
So that's fine but there is one last gotcha.
What happens if I forget my error handler?
Of course, if you write production quality code you'd add an error handler for every single Sub and Function! But if you are playing with some test code you may forget to add an error handler, let's simulate this by commenting out the On Error Goto PythonErrHand line of code. If you then run the code you get the following message box...
and so this is devoid of any rich error information. So just be aware.
No comments:
Post a Comment