[python-win32] Object type puzzle

Mark Hammond skippy.hammond at gmail.com
Tue Jan 3 21:08:36 EST 2023


On 4/01/2023 12:58 am, Bob Kline wrote:
> 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.

This is probably just defined by IDispatch and related interfaces. There 
is a good chance that JScript magically uses multiple interfaces at the 
same time for an object, whereas Python only tends to use one.

I suspect the root of the problem is that Python ends up in 
https://github.com/mhammond/pywin32/blob/main/com/win32com/client/__init__.py#L38, 
and as you can see it only uses the first possible type info for the 
IDispatch. It would be interesting to know if the objects with this 
behaviour have multiple entries there with the one you care about being 
at somewhere other than 0.

But even if that's true, I'm not quite sure what you can do to smooth 
this over - we can't just make that code use, say, `[-1]` - but it still 
would be useful to identify if that's the problem.

Cheers,

Mark

> 
> 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
> _______________________________________________
> python-win32 mailing list
> python-win32 at python.org
> https://mail.python.org/mailman/listinfo/python-win32



More information about the python-win32 mailing list