2010-06-24 20 views
11

que tengo tres piezas de código que estoy trabajando con en el momento:Acceso a objetos COM no registrados de pitón a través de una TLB registrada

  • una aplicación de origen (Main.exe) cerró
  • Una fuente cerrada VB objeto COM implementado como una DLL (comobj.dll)
  • el código que estoy desarrollando en Python

comobj.dll alberga un objeto COM (digamos, 'MainInteract') que me gustaría usar desde Pitón. Ya puedo usar este objeto perfectamente de IronPython, pero debido a otros requisitos necesito usarlo de Python regular. Creo que el mejor método aquí es usar win32com, pero no puedo hacer ningún progreso en absoluto.

En primer lugar, un cierto código IronPython de trabajo:

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) 

Y ahora el código que he tratado en Python normal:

>>> 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) 

También he intentado usar el nombre descriptivo de la 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) 

De hecho, el único éxito que he tenido fue la siguiente:

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) 

pero no estoy seguro de cómo ir desde allí a conseguir un objeto. Creo que mi problema es que necesito cargar el dll en mi proceso y hacer que se registre con el origen COM de mi proceso, para que pueda CoCreateInstance/win32com.client.Dispatch() correctamente en él.

También he visto Activation Contexts mencionado, especialmente cuando se habla de 'no registro COM', pero normalmente en frases como "Windows creará un contexto para usted si especifica las cosas correctas en sus archivos .manifest". Me gustaría evitar los archivos manifiestos si es posible, ya que uno sería necesario en la misma carpeta que el dll de objeto COM (fuente cerrada), y preferiría no soltar ningún archivo en ese directorio si puedo evitarlo.

Gracias por la ayuda.

+0

no sé la respuesta de la parte superior de mi cabeza, pero si se puede hazlo con C++, entonces puedes envolverlo de manera bastante trivial. – ConcernedOfTunbridgeWells

+0

Bueno, sí, supongo que es una opción, pero esperaba evitarla. –

Respuesta

3

Para un módulo de servicio útil que envuelve el caso objeto de DLL, así como otros, ver 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

Este es un método que diseñé para cargar un objeto COM desde una DLL. Se basó en mucha lectura sobre COM, etc. No estoy 100% seguro de las últimas líneas, específicamente d =. Creo que solo funciona si se pasa IID_Dispatch (que se puede ver si el param predeterminado).

Además, creo que este código tiene una fuga - por un lado, el archivo DLL nunca se descarga (use ctypes.windll.kernel32.FreeLibraryW) y creo que el recuento de referencias COM para la fábrica de clases inicial está desactivado por uno, y por lo tanto nunca ser liberado Pero aún así, esto funciona para mi aplicación.

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

probado: esta solución funciona. No sé si se filtra – yanjost

+0

. Puede estar interesado en https://gist.github.com/CBWhiz/4219140, donde CoCreateInstanceFromDll se implementa de la misma manera. –

10

Lo que hice para tener acceso a la biblioteca de tipos Free Download Manager fue la siguiente:

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 

El código anterior imprimir la URL de la primera descarga.

+0

Gracias, Márcio Faustino! ¡Estupendo! ¡Esto funciona muy bien para mí! – ECC

Cuestiones relacionadas