问题
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. Ifbuffer
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