通过注册的 TLB 从 python 访问未注册的 COM 对象
Posted
技术标签:
【中文标题】通过注册的 TLB 从 python 访问未注册的 COM 对象【英文标题】:Accessing unregistered COM objects from python via a registered TLB 【发布时间】:2011-03-07 22:48:52 【问题描述】:我目前正在使用三段代码:
一个闭源应用程序 (Main.exe) 作为 dll (comobj.dll) 实现的闭源 VB COM 对象 我正在用 Python 开发的代码comobj.dll 托管一个我想从 Python 中使用的 COM 对象(可以说是“MainInteract”)。我已经可以从 IronPython 完美地使用这个对象,但由于其他要求,我需要从常规 Python 使用它。我认为这里最好的方法是使用 win32com,但我根本无法取得任何进展。
首先,一些可用的 IronPython 代码:
import clr
import os
import sys
__dir__ = os.path.dirname(os.path.realpath(__file__))
sys.path.insert(0, __dir__)
sys.path.append(r"C:\Path\To\comobj.dll") #This is where the com object dll actually is
clr.AddReferenceToFileAndPath(os.path.join(__dir__, r'comobj_1_1.dll')) #This is the .NET interop assembly that was created automatically via SharpDevelop's COM Inspector
from comobj_1_1 import clsMainInteract
o = clsMainInteract()
o.DoStuff(True)
现在是我在常规 Python 中尝试的代码:
>>> import win32com.client
>>> win32com.client.Dispatch("11111111-comobj_guid_i_got_from_com_inspector")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\Python26\lib\site-packages\win32com\client\__init__.py", line 95, in Dispatch
dispatch, userName = dynamic._GetGoodDispatchAndUserName(dispatch,userName,clsctx)
File "C:\Python26\lib\site-packages\win32com\client\dynamic.py", line 104, in _GetGoodDispatchAndUserName
return (_GetGoodDispatch(IDispatch, clsctx), userName)
File "C:\Python26\lib\site-packages\win32com\client\dynamic.py", line 84, in _GetGoodDispatch
IDispatch = pythoncom.CoCreateInstance(IDispatch, None, clsctx, pythoncom.IID_IDispatch)
pywintypes.com_error: (-2147221164, 'Class not registered', None, None)
我也尝试过使用 TLB 的友好名称:
>>> import win32com.client
>>> win32com.client.Dispatch("Friendly TLB Name I Saw")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\Python26\lib\site-packages\win32com\client\__init__.py", line 95, in Dispatch
dispatch, userName = dynamic._GetGoodDispatchAndUserName(dispatch,userName,clsctx)
File "C:\Python26\lib\site-packages\win32com\client\dynamic.py", line 104, in _GetGoodDispatchAndUserName
return (_GetGoodDispatch(IDispatch, clsctx), userName)
File "C:\Python26\lib\site-packages\win32com\client\dynamic.py", line 84, in _GetGoodDispatch
IDispatch = pythoncom.CoCreateInstance(IDispatch, None, clsctx, pythoncom.IID_IDispatch)
pywintypes.com_error: (-2147221005, 'Invalid class string', None, None)
事实上,我唯一的成功是:
import pythoncom
tlb = pythoncom.LoadRegTypeLib("11111111-comobj_guid_i_got_from_com_inspector",1,1,0)
>>> tlb
<PyITypeLib at 0x00AD7D78 with obj at 0x0025EDF0>
>>> tlb.GetDocumentation(1)
(u'clsMainInteract', None, 0, None)
但我不确定如何从那里获取对象。我认为我的问题是我需要将 dll 加载到我的进程中并让它自己注册到我的进程的 COM 源中,这样我就可以正确地 CoCreateInstance / win32com.client.Dispatch() 就可以了。
我还看到Activation Contexts 被引用,尤其是在谈论“无注册 COM”时,但通常在诸如“如果您在 .manifest 文件中指定正确的内容,Windows 将为您创建上下文”这样的句子中。如果可能的话,我想避免清单文件,因为在与(封闭源)COM 对象 dll 相同的文件夹中需要一个文件,如果可以避免的话,我宁愿不删除该目录中的任何文件。
感谢您的帮助。
【问题讨论】:
我不知道答案在我的脑海中,但如果你能用 C++ 做到这一点,那么你可以相当简单地包装它。 嗯,是的,我想这是一个选择,但我希望避免它。 【参考方案1】:我访问免费下载管理器的类型库的操作如下:
import pythoncom, win32com.client
fdm = pythoncom.LoadTypeLib('fdm.tlb')
downloads_stat = None
for index in xrange(0, fdm.GetTypeInfoCount()):
type_name = fdm.GetDocumentation(index)[0]
if type_name == 'FDMDownloadsStat':
type_iid = fdm.GetTypeInfo(index).GetTypeAttr().iid
downloads_stat = win32com.client.Dispatch(type_iid)
break
downloads_stat.BuildListOfDownloads(True, True)
print downloads_stat.Download(0).Url
上面的代码将打印第一次下载的 URL。
【讨论】:
【参考方案2】:这是我设计的从 DLL 加载 COM 对象的方法。它基于大量关于 COM 等的阅读。我对最后几行不是 100% 确定,特别是 d=。我认为只有在传入 IID_Dispatch 时才有效(您可以查看是否为默认参数)。
此外,我相信这段代码会泄漏 - 一方面,DLL 永远不会被卸载(使用 ctypes.windll.kernel32.FreeLibraryW),而且我相信初始类工厂的 COM 引用计数会减少 1,因此永远不会被释放。但是,这仍然适用于我的应用程序。
import pythoncom
import win32com.client
def CreateInstanceFromDll(dll, clsid_class, iid_interface=pythoncom.IID_IDispatch, pUnkOuter=None, dwClsContext=pythoncom.CLSCTX_SERVER):
from uuid import UUID
from ctypes import OleDLL, c_long, byref
e = OleDLL(dll)
clsid_class = UUID(clsid_class).bytes_le
iclassfactory = UUID(str(pythoncom.IID_IClassFactory)).bytes_le
com_classfactory = c_long(0)
hr = e.DllGetClassObject(clsid_class, iclassfactory, byref(com_classfactory))
MyFactory = pythoncom.ObjectFromAddress(com_classfactory.value, pythoncom.IID_IClassFactory)
i = MyFactory.CreateInstance(pUnkOuter, iid_interface)
d = win32com.client.__WrapDispatch(i)
return d
【讨论】:
链接不再可用 更新链接:gist.github.com/ebnull/4219140【参考方案3】:有关包装 object-from-DLL 情况的有用实用程序模块以及其他情况,请参阅https://gist.github.com/4219140
__all__ = (
####### Class Objects
#CoGetClassObject - Normal, not wrapped
'CoDllGetClassObject', #Get ClassObject from a DLL file
####### ClassFactory::CreateInstance Wrappers
'CoCreateInstanceFromFactory', #Create an object via IClassFactory::CreateInstance
'CoCreateInstanceFromFactoryLicenced', #Create a licenced object via IClassFactory2::CreateInstanceLic
###### Util
'CoReleaseObject', #Calls Release() on a COM object
###### Main Utility Methods
#'CoCreateInstance', #Not wrapped, normal call
'CoCreateInstanceLicenced', #CoCreateInstance, but with a licence key
###### Hacky DLL methods for reg-free COM without Activation Contexts, manifests, etc
'CoCreateInstanceFromDll', #Given a dll, a clsid, and an iid, create an object
'CoCreateInstanceFromDllLicenced', #Given a dll, a clsid, an iid, and a license key, create an object
)
IID_IClassFactory2 = "B196B28F-BAB4-101A-B69C-00AA00341D07"
from uuid import UUID
from ctypes import OleDLL, WinDLL, c_ulong, byref, WINFUNCTYPE, POINTER, c_char_p, c_void_p
from ctypes.wintypes import HRESULT
import pythoncom
import win32com.client
import logging
log = logging.getLogger(__name__)
def _raw_guid(guid):
"""Given a string GUID, or a pythoncom IID, return the GUID laid out in memory suitable for passing to ctypes"""
return UUID(str(guid)).bytes_le
proto_icf2_base = WINFUNCTYPE(HRESULT,
c_ulong,
c_ulong,
c_char_p,
c_ulong,
POINTER(c_ulong),
)
IClassFactory2__CreateInstanceLic = proto_icf2_base(7, 'CreateInstanceLic', (
(1, 'pUnkOuter'),
(1 | 4, 'pUnkReserved'),
(1, 'riid'),
(1, 'bstrKey'),
(2, 'ppvObj'),
), _raw_guid(IID_IClassFactory2))
#--------------------------------
#--------------------------------
def _pc_wrap(iptr, resultCLSID=None):
#return win32com.client.__WrapDispatch(iptr)
log.debug("_pc_wrap: %s, %s"%(iptr, resultCLSID))
disp = win32com.client.Dispatch(iptr, resultCLSID=resultCLSID)
log.debug("_pc_wrap: %s (%s)", disp.__class__.__name__, disp)
return disp
def CoCreateInstanceFromFactory(factory_ptr, iid_interface=pythoncom.IID_IDispatch, pUnkOuter=None):
"""Given a factory_ptr whose interface is IClassFactory, create the instance of clsid_class with the specified interface"""
ClassFactory = pythoncom.ObjectFromAddress(factory_ptr.value, pythoncom.IID_IClassFactory)
i = ClassFactory.CreateInstance(pUnkOuter, iid_interface)
return i
def CoCreateInstanceFromFactoryLicenced(factory_ptr, key, iid_interface=pythoncom.IID_IDispatch, pUnkOuter=None):
"""Given a factory_ptr whose interface is IClassFactory2, create the instance of clsid_class with the specified interface"""
requested_iid = _raw_guid(iid_interface)
ole_aut = WinDLL("OleAut32.dll")
key_bstr = ole_aut.SysAllocString(unicode(key))
try:
obj = IClassFactory2__CreateInstanceLic(factory_ptr, pUnkOuter or 0, c_char_p(requested_iid), key_bstr)
disp_obj = pythoncom.ObjectFromAddress(obj, iid_interface)
return disp_obj
finally:
if key_bstr:
ole_aut.SysFreeString(key_bstr)
#----------------------------------
def CoReleaseObject(obj_ptr):
"""Calls Release() on a COM object. obj_ptr should be a c_void_p"""
if not obj_ptr:
return
IUnknown__Release = WINFUNCTYPE(HRESULT)(2, 'Release', (), pythoncom.IID_IUnknown)
IUnknown__Release(obj_ptr)
#-----------------------------------
def CoCreateInstanceLicenced(clsid_class, key, pythoncom_iid_interface=pythoncom.IID_IDispatch, dwClsContext=pythoncom.CLSCTX_SERVER, pythoncom_wrapdisp=True, wrapas=None):
"""Uses IClassFactory2::CreateInstanceLic to create a COM object given a licence key."""
IID_IClassFactory2 = "B196B28F-BAB4-101A-B69C-00AA00341D07"
ole = OleDLL("Ole32.dll")
clsid_class_raw = _raw_guid(clsid_class)
iclassfactory2 = _raw_guid(IID_IClassFactory2)
com_classfactory = c_void_p(0)
ole.CoGetClassObject(clsid_class_raw, dwClsContext, None, iclassfactory2, byref(com_classfactory))
try:
iptr = CoCreateInstanceFromFactoryLicenced(
factory_ptr = com_classfactory,
key=key,
iid_interface=pythoncom_iid_interface,
pUnkOuter=None,
)
if pythoncom_wrapdisp:
return _pc_wrap(iptr, resultCLSID=wrapas or clsid_class)
return iptr
finally:
if com_classfactory:
CoReleaseObject(com_classfactory)
#-----------------------------------------------------------
#DLLs
def CoDllGetClassObject(dll_filename, clsid_class, iid_factory=pythoncom.IID_IClassFactory):
"""Given a DLL filename and a desired class, return the factory for that class (as a c_void_p)"""
dll = OleDLL(dll_filename)
clsid_class = _raw_guid(clsid_class)
iclassfactory = _raw_guid(iid_factory)
com_classfactory = c_void_p(0)
dll.DllGetClassObject(clsid_class, iclassfactory, byref(com_classfactory))
return com_classfactory
def CoCreateInstanceFromDll(dll, clsid_class, iid_interface=pythoncom.IID_IDispatch, pythoncom_wrapdisp=True, wrapas=None):
iclassfactory_ptr = CoDllGetClassObject(dll, clsid_class)
try:
iptr = CoCreateInstanceFromFactory(iclassfactory_ptr, iid_interface)
if pythoncom_wrapdisp:
return _pc_wrap(iptr, resultCLSID=wrapas or clsid_class)
return iptr
finally:
CoReleaseObject(iclassfactory_ptr)
def CoCreateInstanceFromDllLicenced(dll, clsid_class, key, iid_interface=pythoncom.IID_IDispatch, pythoncom_wrapdisp=True, wrapas=None):
iclassfactory2_ptr = CoDllGetClassObject(dll, clsid_class, iid_factory=IID_IClassFactory2)
try:
iptr = CoCreateInstanceFromFactoryLicenced(iclassfactory2_ptr, key, iid_interface)
if pythoncom_wrapdisp:
return _pc_wrap(iptr, resultCLSID=wrapas or clsid_class)
return iptr
finally:
CoReleaseObject(iclassfactory2_ptr)
【讨论】:
以上是关于通过注册的 TLB 从 python 访问未注册的 COM 对象的主要内容,如果未能解决你的问题,请参考以下文章
msadox28.tlb 在注册时不是有效的 .Net 程序集文件
如何使用 Windows Installer XML 注册 COM 对象
visual studio2010中C#生成的,ArcGIS二次开发的basetool的dll,注册为COM组件tlb文件,并在arcmap中加载使用