Skip to content

win32com.client returns objects witht he wrong interface when using makepy generated modules #1699

@dnicolodi

Description

@dnicolodi

I am using Python 3.8.8 and pywin32 227 on Windows 10. I am trying to do some SAP GUI scripting in Python. All goes well when I don't generate the python modules for the COM interfaces and I rely on dynamic dispatch, however, things break when win32com.client invokes makepy.

A simple example to demonstrates the issue:

import win32com.client
sap = win32com.client.GetObject('SAPGUI')
application = sap.GetScriptingEngine
len(application.Connections)

works. However, after running makepy for the relevant COM module, the same code raises an exception:

  File "C:\Users\nicolo01\Documents\Python\examples\sap.py", line 34, in main
    len(application.Connections)
TypeError: object of type 'GuiComponentCollection' has no len()

Inspecting the module generated by makepy, the Connection property is defined like this:

class _Dsapfewse(DispatchBaseClass):
        ...
	_prop_map_get_ = {
                ...
		# Property 'Connections' is an object of type 'GuiComponentCollection'                
		"Connections": (32900, 2, (13, 0), (), "Connections", '{A464F5B4-CC70-4062-A0A1-6DC9B461D776}'),
                ...
        }

which I think corresponds to this class:

class GuiComponentCollection(CoClassBaseClass): # A CoClass
	CLSID = IID('{A464F5B4-CC70-4062-A0A1-6DC9B461D776}')
	coclass_sources = [
	]
	coclass_interfaces = [
		ISapCollectionTarget,
	]
	default_interface = ISapCollectionTarget

and this interface:

class ISapCollectionTarget(DispatchBaseClass):
	CLSID = IID('{736C7684-ECDA-4984-87A9-AE358AE8823F}')
	coclass_clsid = IID('{A464F5B4-CC70-4062-A0A1-6DC9B461D776}')

	# Result is of type GuiComponent
	def ElementAt(self, Index=defaultNamedNotOptArg):
		ret = self._oleobj_.InvokeTypes(33102, LCID, 1, (13, 0), ((3, 0),),Index
			)
		if ret is not None:
			# See if this IUnknown is really an IDispatch
			try:
				ret = ret.QueryInterface(pythoncom.IID_IDispatch)
			except pythoncom.error:
				return ret
			ret = Dispatch(ret, 'ElementAt', '{ABCC907C-3AB1-45D9-BF20-D3F647377B06}')
		return ret

	# Result is of type GuiComponent
	def Item(self, Index=defaultNamedNotOptArg):
		ret = self._oleobj_.InvokeTypes(0, LCID, 1, (13, 0), ((12, 0),),Index
			)
		if ret is not None:
			# See if this IUnknown is really an IDispatch
			try:
				ret = ret.QueryInterface(pythoncom.IID_IDispatch)
			except pythoncom.error:
				return ret
			ret = Dispatch(ret, 'Item', '{ABCC907C-3AB1-45D9-BF20-D3F647377B06}')
		return ret

	_prop_map_get_ = {
		"Count": (33100, 2, (3, 0), (), "Count", None),
		"Length": (33101, 2, (3, 0), (), "Length", None),
		"Type": (32015, 2, (8, 0), (), "Type", None),
		"TypeAsNumber": (32032, 2, (3, 0), (), "TypeAsNumber", None),
	}
	_prop_map_put_ = {
		"Count" : ((33100, LCID, 4, 0),()),
		"Length" : ((33101, LCID, 4, 0),()),
		"NewEnum" : ((-4, LCID, 4, 0),()),
		"Type" : ((32015, LCID, 4, 0),()),
		"TypeAsNumber" : ((32032, LCID, 4, 0),()),
	}
	# Default method for this class is 'Item'
	def __call__(self, Index=defaultNamedNotOptArg):
		ret = self._oleobj_.InvokeTypes(0, LCID, 1, (13, 0), ((12, 0),),Index
			)
		if ret is not None:
			# See if this IUnknown is really an IDispatch
			try:
				ret = ret.QueryInterface(pythoncom.IID_IDispatch)
			except pythoncom.error:
				return ret
			ret = Dispatch(ret, '__call__', '{ABCC907C-3AB1-45D9-BF20-D3F647377B06}')
		return ret

	def __str__(self, *args):
		return str(self.__call__(*args))
	def __int__(self, *args):
		return int(self.__call__(*args))
	def __iter__(self):
		"Return a Python iterator for this object"
		try:
			ob = self._oleobj_.InvokeTypes(-4,LCID,3,(13, 10),())
		except pythoncom.error:
			raise TypeError("This object does not support enumeration")
		return win32com.client.util.Iterator(ob, '{ABCC907C-3AB1-45D9-BF20-D3F647377B06}')
	#This class has Count() property - allow len(ob) to provide this
	def __len__(self):
		return self._ApplyTypes_(*(33100, 2, (3, 0), (), "Count", None))
	#This class has a __len__ - this is needed so 'if object:' always returns TRUE.
	def __nonzero__(self):
		return True

which seems to implement the right methods and Python interfaces (by the way, it would be cool if __getitem__ could also be implemented for COM interfaces exposing containers). However, I don't really understand how all the pieces are supposed to be glued together, and thus why it does not work as intended. The Connections property return the right type:

type(application.Connections)
win32com.gen_py.5EA428A0-F2B8-45E7-99FA-0E994E82B5BCx0x1x0.GuiComponentCollection

The same think happens for all other properties that return a COM object. I can go on with dynamic dispatching just fine, without the generated modules, but the generated modules are necessary (or at least they built is triggered by) using event dispatching, thus I am kind of stuck.

Please let me know if there is anything more I can provide to help debug the issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions