【发布时间】:2018-06-07 17:33:03
【问题描述】:
环境
Windows 10 + python 3.6.3 64 位(也尝试过 32 位)。我是一名 Python 开发人员,尝试(几乎)第一次使用 COM,并遇到了这个巨大的障碍。
问题
尝试通过win32com 或comtypes 使用在dll(不是我编写的)中实现的IRTDServer 时,我遇到了各种错误。使用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()
结果:
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 和IRTDUpdateEvent 的各种函数和类型定义。但是在这个文件中,两个接口都是win32com.client.DispatchBaseClass的子类,而根据OleViewDotNet,它们应该是IUnknown的子类?
但是,当我尝试再次运行单元测试时,我收到了与以前完全相同的错误。好像查找机制没有找到生成的模块?
另外,GetTypeInfo 返回Not implemented 让我感到震惊。据我了解,win32com 使用该方法(IDispatch COM 接口的一部分)来确定其他接口中所有其他函数的参数和返回类型,包括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()
结果:
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))
我尝试过的其他一些解决方案
(基于谷歌搜索和下面 cmets 中的答案)
- (重新)注册 DLL
- 注册了32位版本的DLL,尝试了python 32位
- 将
python.exe的兼容模式设置为Windows XP SP3 -
试过不实例化IRtdServer,即替换这两行:
self._rtd = self._comObj.IRtdServer() result = self._rtd.ServerStart(self)与:
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 类才能使用该接口,但我不知道该怎么做。 -
我注意到
CreateObject的dynamic参数。代码表明这与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)但是错误和我原来的错误一样:
IRtdServerhas_ctypes.COMError: (-2147352570, 'Unknown name.', (None, None, None, 0, None))
任何帮助或线索将不胜感激。提前谢谢你。
(不知道自己在做什么,)我尝试使用 OleViewDotNet 来查看 DLL:
【问题讨论】:
-
这里我不确定,但是
self._comObj.IRtdServer(self)你真的需要吗?你试过打电话self._comObj.ServerStart(self)吗? -
另一种选择是将您需要的接口放在 CreateObject 的第 4 个参数中:
comtypes.client.CreateObject(clsid, None, None, IRtdServer)。它可能是IRtdServer或"IRtdServer"或者它的 GUID — comtypes 文档在这里不是很清楚。 -
在“我尝试过的其他一些解决方案”下,我尝试了您在最新编辑中所说的几种排列方式。还是行不通。我想我需要使用
dynamic=True来避免编写我自己的 IRtdServer 类。 -
所以你问我们“为什么我不能在不创建类 ID 的情况下访问服务”(注册)。
-
我最终对这个项目采取了不同的方法,使用 win32com 与 Excel 对话并用
=RTD(...)公式填充单元格,然后从单元格中读取值。这是一个额外的步骤,但它有效。为此,我使用 win32com 没有任何问题。
标签: python com pywin32 win32com comtypes