【问题标题】:How to introspect win32com wrapper?如何自省 win32com 包装器?
【发布时间】:2017-02-16 01:10:33
【问题描述】:

我有一个设备,它记录光谱数据并由第 3 方应用程序控制。出于自动化目的,我想使用应用程序的 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 确实返回了实际数据。

如果我没有观察到我的程序随机崩溃并抛出 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 包装器的更多信息?

【问题讨论】:

    标签: python introspection win32com


    【解决方案1】:

    尝试了更多,我终于能够找到解决问题的方法。第一个重要的认识是发现调用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 表示)。根据this 博客条目,相应的第一个数字告诉我们预期的Variant 数据类型。

    我们可以在this 列表中查找这些数字的实际含义。第一个参数是带符号的int16,这是有道理的,因为它指定了帧号。第二个数字的含义如下。

    16396 = 0x400c = VT_VARIANT | VT_BYREF
    

    documentation 告诉我们,VT_VARIANT 的实际含义。

    指定的类型,或者元素的类型或包含的字段必须是 VARIANT

    不是很有启发性,但仍然如此。似乎选择通过ctypes.c_float 并不是一个好的选择。取而代之的是,我现在传递了一个变体,我可能应该这样做,灵感来自 this 讨论。

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

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

    【讨论】:

      猜你喜欢
      • 2013-02-01
      • 2010-11-12
      • 1970-01-01
      • 2017-02-17
      • 2013-08-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-06
      相关资源
      最近更新 更多