环境
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 使用该方法(部分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()
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: