如何使用 python win32com 或 comtypes 中的 COM 访问 IRTDServer?

2023-12-25

环境

Windows 10 + python 3.6.3 64 位(也尝试过 32 位)。我是一名 Python 开发人员,(几乎)第一次尝试使用 COM,但遇到了这个巨大的障碍。

Problem

当我尝试使用在 dll 中实现的 IRTDServer(不是我写的)时,我遇到了各种错误,通过win32com or comtypes. Using win32com事实证明更加困难。我为下面的两个库提供了一个示例单元测试。

从 Excel 2016 访问服务器按预期工作;这将返回预期值:

=RTD("foo.bar", , "STAT1", "METRIC1")

使用 win32com 库的代码

这是一个简单的测试用例,应该连接到服务器,但没有。 (这只是一个版本,因为我已经多次更改它来尝试调试问题。)

from unittest import TestCase

class COMtest(TestCase):
    def test_win32com(self):
        import win32com.client
        from win32com.server.util import wrap

        class RTDclient:
            # are these only required when implementing the server?
            _com_interfaces_ = ["IRTDUpdateEvent"]
            _public_methods_ = ["Disconnect", "UpdateNotify"]
            _public_attrs_ = ["HeartbeatInterval"]

            def __init__(self, *args, **kwargs):
                self._comObj = win32com.client.Dispatch(*args, **kwargs)
            def connect(self):
                self._rtd = win32com.client.CastTo(self._comObj, 'IRtdServer')
                result = self._rtd.ServerStart(wrap(self))
                assert result > 0

            def UpdateNotify(self):
                print("UpdateNotify() callback")
            def Disconnect(self):
                print("Disconnect() called")
            HeartbeatInterval = -1

_rtd = RTDclient("foo.bar")
_rtd.connect()

Result:

Traceback (most recent call last):
  File "env\lib\site-packages\win32com\client\gencache.py", line 532, in EnsureDispatch
    ti = disp._oleobj_.GetTypeInfo()
pywintypes.com_error: (-2147467263, 'Not implemented', None, None)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "test\test.py", line 23, in test_win32com
    _rtd.connect()
  File "test\test.py", line 16, in connect
    self._rtd = win32com.client.CastTo(dispatch, 'IRtdServer')
  File "env\lib\site-packages\win32com\client\__init__.py", line 134, in CastTo
    ob = gencache.EnsureDispatch(ob)
  File "env\lib\site-packages\win32com\client\gencache.py", line 543, in EnsureDispatch
    raise TypeError("This COM object can not automate the makepy process - please run makepy manually for this object")
TypeError: This COM object can not automate the makepy process - please run makepy manually for this object

按照这些指示,我运行了makepy脚本成功:

> env\Scripts\python.exe env\lib\site-packages\win32com\client\makepy.py "foo.bar"
Generating to C:\Users\user1\AppData\Local\Temp\gen_py\3.5\longuuid1x0x1x0.py
Building definitions from type library...
Generating...
Importing module

(出于隐私考虑,我替换了 stackoverflow 上的 UUID。此 UUID 与“foo.bar”的 typelib UUID 相同。)

生成的文件包含两者的各种函数和类型定义IRtdServer and IRTDUpdateEvent。但在这个文件中,两个接口都是win32com.client.DispatchBaseClass,而根据 OleViewDotNet,它们应该是IUnknown?

然而,当我尝试再次运行单元测试时,我收到了与之前完全相同的错误。好像查找机制没有找到生成的模块?

Also, GetTypeInfo返回Not implemented让我感到震惊。据我了解,win32com 使用该方法(部分IDispatchCOM 接口)来确定其他接口中所有其他函数的参数和返回类型,包括IRtdServer。如果不实现,将无法正确确定类型。然而,生成的文件似乎包含这些信息,这也令人困惑。


使用 comtypes 库的代码

from unittest import TestCase

class COMtest(TestCase):
    def test_comtypes(self):
        import comtypes.client

        class RTDclient:
            # are these for win32com only?
            _com_interfaces_ = ["IRTDUpdateEvent"]
            _public_methods_ = ["Disconnect", "UpdateNotify"]
            _public_attrs_ = ["HeartbeatInterval"]

            def __init__(self, clsid):
                self._comObj = comtypes.client.CreateObject(clsid)
            def connect(self):
                self._rtd = self._comObj.IRtdServer()
                result = self._rtd.ServerStart(self)
                assert result > 0

            def UpdateNotify(self):
                print("UpdateNotify() callback")
            def Disconnect(self):
                print("Disconnect() called")
            HeartbeatInterval = -1

_rtd = RTDclient("foo.bar")
_rtd.connect()

Result:

  File "test\test.py", line 27, in test_comtypes
    _rtd.connect()
  File "test\test.py", line 16, in connect
    self._rtd = self._comObj.IRTDServer()
  File "env\lib\site-packages\comtypes\client\dynamic.py", line 110, in __getattr__
    dispid = self._comobj.GetIDsOfNames(name)[0]
  File "env\lib\site-packages\comtypes\automation.py", line 708, in GetIDsOfNames
    self.__com_GetIDsOfNames(riid_null, arr, len(names), lcid, ids)
_ctypes.COMError: (-2147352570, 'Unknown name.', (None, None, None, 0, None))

我尝试过的其他一些解决方案

(基于谷歌搜索和下面评论中的答案)

  • (重新)注册 DLL
  • 注册了32位版本的DLL并尝试了python 32位
  • 设置兼容模式python.exe至 Windows XP SP3
  • 尝试不实例化 IRtdServer,即替换这两行:

    self._rtd = self._comObj.IRtdServer()
    result = self._rtd.ServerStart(self)
    

    with:

    result = self._comObj.ServerStart(self)
    

    这次的错误是:

    TypeError: 'NoneType' object is not callable
    

    这似乎表明ServerStart函数存在,但未定义? (看起来真的很奇怪。这个谜一定还有更多。)

  • 尝试通过interface="IRtdServer"参数为CreateObject:

    def __init__(self, clsid):
        self._comObj = comtypes.client.CreateObject(clsid, interface="IRtdServer")
    def connect(self):
        result = self._comObj.ServerStart(self)
        ...
    

    收到的错误是:

      File "test\test.py", line 13, in __init__
        self._comObj = comtypes.client.CreateObject(clsid, interface="IRtdServer")
      File "env\lib\site-packages\comtypes\client\__init__.py", line 238, in CreateObject
        obj = comtypes.CoCreateInstance(clsid, clsctx=clsctx, interface=interface)
      File "env\lib\site-packages\comtypes\__init__.py", line 1223, in CoCreateInstance
        p = POINTER(interface)()
    TypeError: Cannot create instance: has no _type_
    

    跟踪代码在comtypes库,这似乎表明接口参数需要一个接口类,而不是一个字符串。我发现了定义的各种接口comtypes图书馆:IDispatch, IPersist, IServiceProvider。都是以下类的子类IUnknown。根据 OleViewDotNet 的说法,IRtdServer也是一个子类IUnknown。这让我相信我需要在 python 中类似地编写一个 IRtdServer 类才能使用该接口,但我不知道该怎么做。

  • 我注意到dynamic的参数CreateObject。该代码表明这是互斥的interface参数,所以我尝试了:

    def __init__(self, clsid):
        self._comObj = comtypes.client.CreateObject(clsid, dynamic=True)
    def connect(self):
        self._rtd = self._comObj.IRtdServer()
        result = self._rtd.ServerStart(self)
    

    但错误与我原来的错误相同:IRtdServer has _ctypes.COMError: (-2147352570, 'Unknown name.', (None, None, None, 0, None))

任何帮助或线索将不胜感激。先感谢您。


(不太知道我在做什么,)我尝试使用 OleViewDotNet 来查看 DLL:



我遇到了同样的问题。

我还尝试使用 win32com 让 excel 运行,说实话,这有点不稳定......我什至不能碰我的 Excel。

因此我花了一些时间研究这个问题。问题出在 CastTo 上。认为您(和我)加载的 COM 对象不包含足够的信息来进行转换(某些方法如 GetTypeInfo 未实现等...)

因此,我创建了一个包装器,使这些 COM 对象的方法可调用……这并不明显。这似乎对我有用。

客户端代码是从一个名为 Pyrtd 的项目修改而来的,该项目由于各种原因而无法工作(认为是由于 RTD 模型的更改......RefreshData 的返回现在完全不同)。

import functools

import pythoncom
import win32com.client
from win32com import universal
from win32com.client import gencache
from win32com.server.util import wrap


EXCEL_TLB_GUID = '{00020813-0000-0000-C000-000000000046}'
EXCEL_TLB_LCID = 0
EXCEL_TLB_MAJOR = 1
EXCEL_TLB_MINOR = 4

gencache.EnsureModule(EXCEL_TLB_GUID, EXCEL_TLB_LCID, EXCEL_TLB_MAJOR, EXCEL_TLB_MINOR)

universal.RegisterInterfaces(EXCEL_TLB_GUID,
                             EXCEL_TLB_LCID, EXCEL_TLB_MAJOR, EXCEL_TLB_MINOR,
                             ['IRtdServer', 'IRTDUpdateEvent'])


# noinspection PyProtectedMember
class ObjectWrapperCOM:
    """
    This object can act as a wrapper for an object dispatched using win32com.client.Dispatch
    Sometimes the object written by 3rd party is not well constructed that win32com will not be able to obtain
    type information etc in order to cast the object to a certain interface. win32com.client.CastTo will fail.

    This wrapper class will enable the object to call its methods in this case, even if we do not know what exactly
    the wrapped object is.
    """
    LCID = 0x0

    def __init__(self, obj):
        self._impl = obj  # type: win32com.client.CDispatch

    def __getattr__(self, item):
        flags, dispid = self._impl._find_dispatch_type_(item)
        if dispid is None:
            raise AttributeError("{} is not a valid property or method for this object.".format(item))
        return functools.partial(self._impl._oleobj_.Invoke, dispid, self.LCID, flags, True)


# noinspection PyPep8Naming
class RTDUpdateEvent:
    """
    Implements interface IRTDUpdateEvent from COM imports
    """
    _com_interfaces_ = ['IRTDUpdateEvent']
    _public_methods_ = ['Disconnect', 'UpdateNotify']
    _public_attrs_ = ['HeartbeatInterval']

    # Implementation of IRTDUpdateEvent.
    HeartbeatInterval = -1

    def __init__(self, event_driven=True):
        self.ready = False
        self._event_driven = event_driven

    def UpdateNotify(self):
        if self._event_driven:
            self.ready = True

    def Disconnect(self):
        pass


class RTDClient:
    """
    Implements a Real-Time-Data (RTD) client for accessing COM data sources that provide an IRtdServer interface.
    """

    MAX_REGISTERED_TOPICS = 1024

    def __init__(self, class_id):
        """
        :param classid: can either be class ID or program ID
        """
        self._class_id = class_id
        self._rtd = None
        self._update_event = None

        self._topic_to_id = {}
        self._id_to_topic = {}
        self._topic_values = {}
        self._last_topic_id = 0

    def connect(self, event_driven=True):
        """
        Connects to the RTD server.

        Set event_driven to false if you to disable update notifications.
        In this case you'll need to call refresh_data manually.
        """

        dispatch = win32com.client.Dispatch(self._class_id)
        self._update_event = RTDUpdateEvent(event_driven)
        try:
            self._rtd = win32com.client.CastTo(dispatch, 'IRtdServer')
        except TypeError:
            # Automated makepy failed...no detailed construction available for the class
            self._rtd = ObjectWrapperCOM(dispatch)

        self._rtd.ServerStart(wrap(self._update_event))

    def update(self):
        """
        Check if there is data waiting and call RefreshData if necessary. Returns True if new data has been received.
        Note that you should call this following a call to pythoncom.PumpWaitingMessages(). If you neglect to
        pump the message loop you'll never receive UpdateNotify callbacks.
        """
        # noinspection PyUnresolvedReferences
        pythoncom.PumpWaitingMessages()
        if self._update_event.ready:
            self._update_event.ready = False
            self.refresh_data()
            return True
        else:
            return False

    def refresh_data(self):
        """
        Grabs new data from the RTD server.
        """

        (ids, values) = self._rtd.RefreshData(self.MAX_REGISTERED_TOPICS)
        for id_, value in zip(ids, values):
            if id_ is None and value is None:
                # This is probably the end of message
                continue
            assert id_ in self._id_to_topic, "Topic ID {} is not registered.".format(id_)
            topic = self._id_to_topic[id_]
            self._topic_values[topic] = value

    def get(self, topic: tuple):
        """
        Gets the value of a registered topic. Returns None if no value is available. Throws an exception if
        the topic isn't registered.
        """
        assert topic in self._topic_to_id, 'Topic %s not registered.' % (topic,)
        return self._topic_values.get(topic)

    def register_topic(self, topic: tuple):
        """
        Registers a topic with the RTD server. The topic's value will be updated in subsequent data refreshes.
        """
        if topic not in self._topic_to_id:
            id_ = self._last_topic_id
            self._last_topic_id += 1

            self._topic_to_id[topic] = id_
            self._id_to_topic[id_] = topic

            self._rtd.ConnectData(id_, topic, True)

    def unregister_topic(self, topic: tuple):
        """
        Un-register topic so that it will not get updated.
        :param topic:
        :return:
        """
        assert topic in self._topic_to_id, 'Topic %s not registered.' % (topic,)
        self._rtd.DisconnectData(self._topic_to_id[topic])

    def disconnect(self):
        """
        Closes RTD server connection.
        :return:
        """
        self._rtd.ServerTerminate()
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何使用 python win32com 或 comtypes 中的 COM 访问 IRTDServer? 的相关文章

随机推荐

  • 如何在 A-Frame 中启用抗锯齿功能?

    抗锯齿可以平滑曲线和对角线上的锯齿状边缘 如何在 A 型框架上启用抗锯齿功能 你可以做
  • Feathers.js / Sequelize -> 具有两个模型之间关系的服务

    我已经通过sequelize 使feeas js 与mysql 一起工作 这是可行的 我可以从表中收集数据 下一步是在模型中定义 连接 我有一个包含 status id 和 country id 列的表 这些列引用元数据表中的 ID 在 S
  • 在重复测量数据中,如何进行子集化以选择匹配的病例和对照?

    我有一组按家庭聚类的数据 研究问题是同一家庭中具有不同特征 x 的 2 个人是否具有相同的二元 是 否 结果 y 在一些家庭中 所有成员都对 y 说 是 在其他家庭中 对于 y 有些是 是 有些是 否 我只想得到结果状态不一致的家庭 我猜测
  • 添加隐藏字段以结账并通过订单进行处理

    我想在结帐流程中添加一个验证码 该验证码是只读 或不可见 且预先填写并固定在订单上 客户需要此代码来验证订单 我将自定义数组添加到 woocommerce checkout fields 过滤器的计费字段中 VID fields billi
  • 使用 POI API 在 Excel 中显示百分比值

    我需要在 Excel 单元格中显示一个百分比格式的值 例如喜欢12 3 默认情况下 该值显示为文本 但我需要将其显示为数字 实现这一目标的适当方法是什么 你需要 将数据设置为数字 浮点 而不是文本 将单元格格式指定为百分比 就像是 cell
  • 哪个工具可以为我的数据库构建一个简单的 Web 前端 [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 我是一名 SQL Server DBA 我想通过 Web 浏览器访问一个数据库 它将在 Intranet 内部使用 只需调用 SQL Serve
  • fuslogvw中的Default/NativeImage是什么意思?

    我偶尔使用 fuslogvw 来追踪加载程序集的问题 经过我所有时间的使用 似乎文件夹 NativeImage 具有加载我的 dll 的本机图像的结果 当未找到本机映像 通常没有 时 我认为它会查找 net 程序集 默认 文件夹是我通常关心
  • 为什么我收到这个 jsfiddle 错误,document.write 可以是 eval 的一种形式

    我正在测试我在看书时发现的代码 我在 JS fiddle 中测试时遇到此错误 document write可以是 eval 的一种形式 var text p This is b bold lt b gt lt p gt lt body gt
  • 设计 - 超时不起作用

    在以下情况下 Devise 不会使用户超时 用户登录 关闭选项卡 然后在超时 X 分钟内重新访问该 URL 用户仍处于登录状态 如果选项卡已打开并且稍后刷新 单击 则超时可以正常工作 这意味着timeoutable模块在这种情况下可以正常工
  • 为什么 sortBy 转换会触发 Spark 作业?

    根据 Spark 文档 只有 RDD 操作可以触发 Spark 作业 并且在调用操作时会延迟评估转换 我看到sortBy转换函数会立即应用 并在 SparkUI 中显示为作业触发器 为什么 sortBy是使用实现的sortByKey这取决于
  • 如何在Android上实现旋转控件?

    有人对如何创建旋转控件有任何想法吗 看起来像这样的东西 http www raywenderlich com 9864 how to create a rotating wheel control with uikit http www r
  • 在 Dart 中将任意长的十六进制字符串转换为数字?

    我需要将 8 个字符的十六进制子字符串转换为整数列表 例如 我可能有字符串 001479B70054DB6E001475B3 由以下子字符串组成 001479B7 1341879 decimal 0054DB6E 5561198 decim
  • 关于 putenv() 和 setenv() 的问题

    我一直在思考环境变量 并有一些问题 观察结果 putenv char string 这个电话似乎有致命的缺陷 因为它不会复制传递的字符串 所以您无法使用本地调用它 并且不能保证堆分配的字符串不会被覆盖或意外删除 此外 虽然我还没有测试过 由
  • 媒体播放器准备失败

    过去 8 个小时我一直在尝试开发一个简单的录音 播放应用程序 我希望能够以 3GP 格式录制音频 然后将其自动加载到MediaPlayer这样我就可以播放它 我 80 确定它正在录制并保存到代码中指定的位置 但是当我尝试将其加载到我的媒体播
  • 在 Gear S2 设备上启动 Web 应用程序时出现错误 81

    我正在使用 Tizen SDK 2 4 0 Rev5 在 Gear S2 上启动演示应用程序 HelloTizen wearable 2 3 但失败并出现以下错误 Unknown Error 81 Unknown Error 81 当我尝试
  • Javascript 模板 - 深度嵌套是否可能

    我正在构建一个任务应用程序 为了好玩 我只是坐下来思考这个问题 我在这里用文字把这个问题记在心里 模型非常简单 它包含以下集合Project 每个项目包含一个TaskList这些任务列表是nestable即例如一个任务设计首页可以有设计标头
  • std::unique_ptr 作为 std::thread 中函数的参数[重复]

    这个问题在这里已经有答案了 所以我想通过std unique ptr作为在单独线程中启动的函数的参数 我在编译时收到一个奇怪的错误 内容如下 1 gt c program files x86 microsoft visual studio
  • 如何正确使用“cv2.putText”在图像上绘制阿拉伯文本? (Python+OpenCV)

    我使用 python cv2 window10 python3 6 在图像中写入文本 当文本是英文时它可以工作 但是当我使用阿拉伯文本时它会在图像中写入混乱的代码 下面是我的代码 import cv2 import numpy as np
  • fork() 和 vfork() 有什么区别?

    有什么区别fork http pubs opengroup org onlinepubs 9699919799 functions fork html and vfork http man7 org linux man pages man2
  • 如何使用 python win32com 或 comtypes 中的 COM 访问 IRTDServer?

    环境 Windows 10 python 3 6 3 64 位 也尝试过 32 位 我是一名 Python 开发人员 几乎 第一次尝试使用 COM 但遇到了这个巨大的障碍 Problem 当我尝试使用在 dll 中实现的 IRTDServe