[python-win32] Object type puzzle
Bob Kline
bkline at rksystems.com
Tue Jan 3 08:58:33 EST 2023
I've made good progress on our project to port our XMetaL JScript
macros to Python (see the thread from about a month ago referring to
the project). All the macros (a couple of hundred of them) have been
rewritten in Python, and they all appear to work correctly (though
testing in earnest is still to come).
One problem we ran into was that there are a number of XMetaL API
calls which are documented to return an object of a specific type but
which actually return an interface to the base type of the documented
type. For example, the Add() method of a CommandBarControls object is
documented to return a CommandBarButton object if the parameter
specifying the control type is 1, but a CommandBarPopup if the
parameter passed is 5. However, regardless of which value that
parameter is given, what is actually returned is a CommandBarControl
object, that is, an object of the base class. Clearly that
documentation assumes that the interface navigation between base and
derived classes will be transparent to the scripting code.
Here's the mystery part. When we used the properties of the returned
object in our JScript macros, the code worked as advertised. However,
the equivalent Python code blows up, with an error claiming that we
are trying to use a property which our object does not possess. And
the error message says that our object is a CommandBarControl object,
not a CommandBarButton or CommandBarPopup object. So the mystery is:
why does the JScript code work, when Python code doing the same thing
does not?
The vendor came up with a workaround, which is for us to explicitly
cast the interface which is returned to the type we need to use
ourselves. For example, replacing
button = toolbar.Controls.Add(1)
with
control = toolbar.Controls.Add(1)
button = win32com.client.CastTo(control, "CommandBarButton")
This works. We have over a dozen places in our code where we've had to
apply this workaround. However, I would like to solve the mystery if
that's possible, because I always prefer knowing how and why the tools
we use do what they do. Cuts down on the number of unpleasant
surprises. :-}
I can think of two possible explanations.
The first would be that the vendor is detecting when the scripting
engine is JScript and is behaving differently in that case, performing
the cast and actually returning the interface to the specific derived
class instead of the interface to the base class. It's hard to imagine
what the incentive would be for the vendor to do this (have the API
behave differently depending on what the scripting engine is). Also, I
asked them point blank if that's what's going on, and they said that
it isn't.
The other, more plausible explanation would be that JScript is
navigating between the interfaces using the introspection capabilities
provided by the COM architecture. When it sees a method call or
property access on an interface for which the method or property isn't
implemented, but for which derived classes exist which do implement
it, it figures out which one supports the method or property,
effectively doing the casting for us. The weak link in this
explanation is the question: what would the scripting engine do if
more than one derived class implemented the method invoked or the
property accessed? Presumably that would trigger an exception.
Normally I would clear up such a puzzle by reading the specification
for the underlying framework. However, when I asked the vendor for the
Windows Scripting Interface specification used to expose their
scripting API, they told me that such a document doesn't exist.
Assuming the second explanation above is correct, why is the Python
scripting support not performing the interface navigation which (for
example) JScript and VBScript are doing, and which the documentation
for the XMetaL APIs appears to assume will happen? Or is there a third
explanation for the mystery which has eluded me?
Thanks,
Bob Kline
More information about the python-win32
mailing list