我有一个设备,它记录光谱数据并由第三方应用程序控制。为了自动化,我想使用应用程序的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
保持不变,但函数{{1}确实会返回实际数据。
我不关心这些细微之处,如果我没有观察到我的程序随机崩溃抛出GetFrame
并因此在代码的这一点指示内存泄漏。所以我怀疑每次调用MemoryError
时,一些内存被分配给缓冲区但从未被释放,因为GetFrame
以某种方式弄乱了API包装。
这种推理引出了我的实际问题:我如何反省包装并理解它的作用?到目前为止,我找不到任何提示,win32com
生成的代码存储在任何文件中,但也许我只是没有找到正确的位置。
在IPython中,我也尝试使用win32com
获取信息,但它没有返回任何实现:
doc.GetFrame??
我还可以尝试获取有关API包装器的更多信息吗?
答案 0 :(得分:3)
尝试更多,我终于能够找到解决问题的方法。第一个重要的实现是发现调用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邮件列表中的this消息,InvokeTypes
与Invoke
非常相似,后者又是IDispatch::Invoke
的重新实现。可以找到集成在Python中的C ++实现的源代码here。
通过这个C ++实现也解释了,在我原来的问题中困扰我的是:Invoke
的Python版本明确地将byref参数转换为返回值。因此,至少应该没有内存泄漏,我在开始时就怀疑它。
现在我们可以从参数类型中学到什么?必要的信息存储在元组((2, 1), (16396, 3))
中。我们有两个参数,其中第一个是仅输入参数(由1
表示),而第二个是输入和输出参数(由3 = 1 | 2
表示)。根据{{3}}博客条目,相应的第一个数字告诉我们预期的this数据类型。
我们可以在Variant
列表中查找数字的实际含义。第一个参数是带符号的int16
,这是有道理的,因为它指定了帧编号。第二个数字具有以下含义。
16396 = 0x400c = VT_VARIANT | VT_BYREF
this告诉我们,VT_VARIANT
实际意味着什么。
指定的类型,或元素或包含字段的类型必须是VARIANT
不是超级有启发性,但仍然。似乎传递ctypes.c_float
的选择并不是一个好的选择。相反,我现在正在传递一个变体,我可能应该受到documentation讨论的启发。
var = win32com.client.VARIANT(pythoncom.VT_VARIANT | pythoncom.VT_NULL | pythoncom.VT_BYREF, None)
spectrum = doc.GetFrame(frame, var)
自从进行此更改后,我不再观察到此代码部分的崩溃,因此原始问题已经解决了。