How to introspect win32com wrapper?

安稳与你 提交于 2020-01-23 10:57:52

问题


I have a device, which records spectroscopic data and is controlled by a 3rd-party application. For automization purposes, I want to use the COM interface of the application to retrieve the data in Python. Since there is no proper documentation for using the API from Python, I collected the following code from different web sources, which successfully obtains the first frame:

comtypes.client.GetModule(('{1A762221-D8BA-11CF-AFC2-508201C10000}', 3, 11))
import comtypes.gen.WINX32Lib as WinSpecLib
win32com.client.pythoncom.CoInitialize()
doc = win32com.client.Dispatch("WinX32.DocFile")

buffer = ctypes.c_float()
frame = 1
spectrum = doc.GetFrame(frame, buffer)

However, the call to GetFrame is inconsistent with its definition in Visual Basic, which is provided by the manufacturer:

Sub GetFrame(frame As Integer, buffer As Variant)

GetFrame copies the data from a document into a Visual Basic array. If buffer is an empty Variant, GetFrame creates an array of the proper size and data type and sets buffer to point to it before copying the data.

This means that in Visual Basic the variable buffer is filled with data while the function GetFrame has no return value, whereas in Python buffer remains unchanged but the function GetFrame does return the actual data.

I wouldn't care about such subtleties, if I hadn't observed random crashes of my program throwing a MemoryError and thus indicating a memory leak at this very point of the code. So my suspicion is that for each call to GetFrame some memory is allocated for buffer but never released, because win32com somehow messed up the API wrapping.

That reasoning leads me to my actual question: How can I introspect that wrapper and understand what it does? So far, I could not find any hints that the code generated by win32com is stored in any file, but maybe I just have not been looking at the right places.

In IPython I also tried to get information using doc.GetFrame??, but it did not return any implementation:

Signature: doc.GetFrame(frame=<PyOleMissing object at 0x06F20BC8>, FrameVariant=<PyOleMissing object at 0x06F20BC8>)
Docstring: <no docstring>
File:      c:\programming\python\src\<comobject winx32.docfile>
Type:      method

What else can I try to get more information about the API wrapper?


回答1:


Trying around more, I was finally able to find a solution to my problem. The first important realization was the finding that calling EnsureDispatch instead of Dispatch gives me access to the wrapper generated by win32com.

>>> import win32com.client
>>> doc = win32com.client.gencache.EnsureDispatch ("WinX32.DocFile")
>>> print(doc.GetFrame.__module__)
'win32com.gen_py.1A762221-D8BA-11CF-AFC2-508201C10000x0x3x12.IDocFile4'

In my case the corresponding file was located in the following folder:

C:\WinPython\WinPython-32bit-3.5.2.2\python-3.5.2\Lib\site-packages\win32com\gen_py\1A762221-D8BA-11CF-AFC2-508201C10000x0x3x12

The implementation of GetFrame looks as follows.

def GetFrame(self, frame=defaultNamedNotOptArg, FrameVariant=defaultNamedNotOptArg):
    'Get Frame Data'
    return self._ApplyTypes_(10, 1, (24, 0), ((2, 1), (16396, 3)), 'GetFrame', None, frame, FrameVariant)

So the magic is in method _ApplyTypes_. This method itself is defined in win32com\client\__init__.

def _ApplyTypes_(self, dispid, wFlags, retType, argTypes, user, resultCLSID, *args):
    return self._get_good_object_(
        self._oleobj_.InvokeTypes(dispid, 0, wFlags, retType, argTypes, *args),
        user, resultCLSID)

We can see that everything is basically passed to InvokeTypes. According to this message on the Python-win32 mailing list, InvokeTypes is very similar to Invoke, which in turn is a re-implementation of IDispatch::Invoke. The source code of the C++ implementation integrated in Python can be found here.

Going through this C++ implementation also explains, what bothered me in my original question: The Python version of Invoke explicitly turns byref arguments into return values. Hence, at least, there should be no memory leak, which I suspected in the beginning.

Now what can we learn about the argument types? The necessary information is stored in the tuple ((2, 1), (16396, 3)). We have two arguments, of which the first is an input only argument (indicated by 1), while the second is an input and output argument (indicated by 3 = 1 | 2). According to this blog entry, the respective first numbers tell us the kind of Variant datatype that is expected.

We can look up in this list, what the numbers actually mean. The first argument is a signed int16, which makes sense, since it specifies the frame number. The second number has the following meaning.

16396 = 0x400c = VT_VARIANT | VT_BYREF

The documentation tells us, what VT_VARIANT actually means.

Either the specified type, or the type of the element or contained field MUST be VARIANT

Not super instructive, but still. It seems that the choice to pass a ctypes.c_float is not really a good choice. Instead, I am now passing a variant, as I probably should, inspired by this discussion.

var = win32com.client.VARIANT(pythoncom.VT_VARIANT | pythoncom.VT_NULL | pythoncom.VT_BYREF, None)
spectrum = doc.GetFrame(frame, var)

Since making this change, I have no longer observed crashes of this code part, so the original question is solved for me.



来源:https://stackoverflow.com/questions/40660996/how-to-introspect-win32com-wrapper

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!