如何自省win32com包装器? [英] How to introspect win32com wrapper?
问题描述
我有一个设备,该设备可以记录光谱数据并由第三方应用程序控制.为了实现自动化,我想使用应用程序的COM接口来检索Python中的数据.由于没有使用Python使用API的适当文档,因此我从不同的Web来源收集了以下代码,这些代码成功获取了第一帧:
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)
但是,对GetFrame
的调用与制造商提供的Visual Basic中的定义不一致:
Sub GetFrame(frame As Integer, buffer As Variant)
GetFrame
将数据从文档复制到Visual Basic数组中.如果buffer
是一个空的Variant,则GetFrame
将创建一个具有适当大小和数据类型的数组,并在复制数据之前将缓冲区设置为指向该数组.
这意味着在Visual Basic中,变量buffer
被数据填充,而函数GetFrame
没有返回值,而在Python中,buffer
保持不变,但函数GetFrame
确实返回了实际数据. /p>
如果我没有观察到程序抛出MemoryError
的随机崩溃并因此指示代码此刻内存泄漏的情况,我将不会在乎这些细微之处.因此,我怀疑每次调用GetFrame
都会为缓冲区分配一些内存,但从未释放,因为win32com
某种程度上弄乱了API包装.
这种推理使我想到了一个实际的问题:我该如何对包装器进行自省并了解其作用?到目前为止,我找不到任何暗示win32com
生成的代码存储在任何文件中,但也许我只是没有找到正确的地方.
在IPython中,我还尝试使用doc.GetFrame??
获取信息,但未返回任何实现:
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
我还能尝试获得有关API包装程序的更多信息吗?
通过更多尝试,我终于能够找到解决问题的方法.第一个重要的发现是发现,调用EnsureDispatch
而不是Dispatch
可以访问由win32com
生成的包装器.
>>> import win32com.client
>>> doc = win32com.client.gencache.EnsureDispatch ("WinX32.DocFile")
>>> print(doc.GetFrame.__module__)
'win32com.gen_py.1A762221-D8BA-11CF-AFC2-508201C10000x0x3x12.IDocFile4'
在我的情况下,相应的文件位于以下文件夹中:
C:\WinPython\WinPython-32bit-3.5.2.2\python-3.5.2\Lib\site-packages\win32com\gen_py\1A762221-D8BA-11CF-AFC2-508201C10000x0x3x12
GetFrame
的实现如下所示.
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)
所以魔术出在方法_ApplyTypes_
中.此方法本身在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)
我们可以看到所有内容基本上都传递给了InvokeTypes
.根据Python-win32邮件列表上的此消息,InvokeTypes
与Invoke
非常相似,后者又是 Variant
数据类型.
我们可以在此列表中查找数字的实际含义.第一个参数是带符号的int16
,这很有意义,因为它指定了帧号.第二个数字具有以下含义.
16396 = 0x400c = VT_VARIANT | VT_BYREF
文档告诉我们VT_VARIANT
的实际含义.
指定的类型,元素的类型或所包含字段的类型必须为VARIANT
不是超级有启发性,但仍然如此.似乎通过ctypes.c_float
的选择实际上并不是一个好选择.取而代之的是,我现在正在传递一种变体,可能应该受到此讨论.
var = win32com.client.VARIANT(pythoncom.VT_VARIANT | pythoncom.VT_NULL | pythoncom.VT_BYREF, None)
spectrum = doc.GetFrame(frame, var)
由于进行了此更改,因此我不再观察到此代码部分的崩溃,因此原来的问题已为我解决.
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?
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.
这篇关于如何自省win32com包装器?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!