2010-06-24 2 views
11

я три куска кода, что я работаю с на данный момент:Доступ к незарегистрированным COM объектов из питона с помощью зарегистрированного TLB

  • Замкнутых исходное приложение (main.exe)
  • Закрытом источник реализован в виде DLL (comobj.dll) VB COM объект
  • код, который я развиваю в Python

comobj.dll хостов COM объект (позволяет сказать, «MainInteract»), что я хотел бы использовать от Python. Я уже могу использовать этот объект совершенно отлично от 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», но обычно в таких предложениях, как «Windows создаст для вас контекст, если вы укажете нужный материал в файлах .manifest». Я хотел бы, если это возможно, избегать файлов манифеста, поскольку один из них потребуется в той же папке, что и DLL COM-объекта (закрытый источник), и я бы предпочел не удалять файлы в этом каталоге, если я могу его избежать.

Спасибо за помощь.

+0

Я не знаю ответа у верхней части моей головы, но если вы можете сделайте это с помощью C++, тогда вы можете довольно тривиально обернуть это. – ConcernedOfTunbridgeWells

+0

Ну да, я полагаю, что это вариант, но я надеялся избежать этого. –

ответ

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

Вот метод, который я разработал для загрузки COM-объекта из DLL. Это было основано на большом чтении о COM и т. Д. Я не уверен на 100% о последних строках, в частности d =. Я думаю, что работает только при передаче IID_Dispatch (что вы можете увидеть, если параметр по умолчанию).

Кроме того, я считаю, что этот код протекает - например, DLL никогда не разгружается (используйте ctypes.windll.kernel32.FreeLibraryW), и я считаю, что COM ref считает, что исходная фабрика классов отключена на единицу, и, следовательно, никогда не освобождаются. Но все же это работает для моего приложения.

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

+0

Возможно, вас заинтересует https://gist.github.com/CBWhiz/4219140, где CoCreateInstanceFromDll реализован во многом таким же образом. –

10

Что я сделал, чтобы получить доступ к библиотеке типов Free Download Manager был следующим:

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árcio Faustino! Большой! Это очень хорошо для меня! – ECC