如何反省win32com包装器?

时间:2016-11-17 17:08:25

标签: python introspection win32com

我有一个设备,它记录光谱数据并由第三方应用程序控制。为了自动化,我想使用应用程序的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包装器的更多信息吗?

1 个答案:

答案 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消息,InvokeTypesInvoke非常相似,后者又是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)

自从进行此更改后,我不再观察到此代码部分的崩溃,因此原始问题已经解决了。