Доступ к незарегистрированным COM-объектам из python через зарегистрированный TLB - PullRequest
11 голосов
/ 24 июня 2010

У меня есть три кода, с которыми я сейчас работаю:

  • Приложение с закрытым исходным кодом (Main.exe)
  • COM-объект VB с закрытым исходным кодом, реализованный как dll (comobj.dll)
  • Код, который я разрабатываю на 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 () на нем.

Я также видел Контексты активации, на которые ссылаются , особенно когда речь идет о «без регистрации COM», но обычно в предложениях типа «Windows создаст для вас контекст, если вы укажете нужные вещи в своем». файлы манифеста ". Я хотел бы по возможности избегать файлов манифеста, так как они потребуются в той же папке, что и dll COM-объекта (с закрытым исходным кодом), и я бы не стал удалять файлы в этом каталоге, если смогу избежать этого.

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

Ответы [ 3 ]

11 голосов
/ 14 мая 2011

То, что я сделал для доступа к библиотеке типов 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-адрес первой загрузки.

8 голосов
/ 28 июня 2010

Вот метод, который я разработал для загрузки COM-объекта из DLL.Это было основано на большом прочтении о 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
3 голосов
/ 06 декабря 2012

Полезный служебный модуль, который упаковывает регистр объекта из 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)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...