2010-06-24 122 views
11

我有代碼三塊,我要和目前的工作:通過註冊TLB訪問蟒蛇未註冊的COM對象

  • 封閉源代碼的應用程序(MAIN.EXE)
  • 封閉源代碼我在Python正在開發的DLL(comobj.dll)實現VB COM對象
  • 代碼

comobj.dll主機的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所在的文件夾中需要一個文件夾,並且如果我可以避免它,我寧願不刪除任何文件。

感謝您的幫助。

+0

我不知道答案了我的頭頂,但如果你能用C++來做,那麼你可以很簡單地包裝它。 – ConcernedOfTunbridgeWells 2010-06-24 17:46:12

+0

好的,我想這是一個選擇,但我希望避免它。 – 2010-06-24 18:27:41

回答

3

爲包裝的對象從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) 
8

這是我設計用來從DLL加載COM對象的方法。它基於大量關於COM的閱讀等。我不是100%確定最後一行,特別是d =。我認爲只有在IID_Dispatch被傳入時纔可以使用(你可以看到默認參數)。另外,我相信這段代碼會泄漏 - 例如,DLL永遠不會被卸載(使用ctypes.windll.kernel32.FreeLibraryW),並且我相信初始類工廠的COM引用計數會被關閉,因此永遠不會被釋放。但是,這仍然適用於我的應用程序。

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 
+0

測試:此解決方案有效。不知道是否泄漏 – yanjost 2013-03-26 08:35:40

+0

您可能會對https://gist.github.com/CBWhiz/4219140感興趣,其中CoCreateInstanceFromDll的實現方式大致相同。 – 2013-03-26 17:59:17

10

我所做的訪問免費下載管理器的類型庫爲以下幾點:

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。

+0

謝謝,MárcioFaustino!大!這對我來說很好! – ECC 2014-07-22 11:31:02