通过已注册的 TLB 从 python 访问未注册的 COM 对象 [英] Accessing unregistered COM objects from python via a registered TLB

查看:36
本文介绍了通过已注册的 TLB 从 python 访问未注册的 COM 对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在使用三段代码:

I have three pieces of code that i'm working with at the moment:

  • 一个闭源应用程序 (Main.exe)
  • 作为 dll (comobj.dll) 实现的闭源 VB COM 对象
  • 我正在用 Python 开发的代码

comobj.dll 托管一个我想从 Python 中使用的 COM 对象(可以说是MainInteract").我已经可以从 IronPython 完美地使用这个对象,但由于其他要求,我需要从常规 Python 使用它.我认为这里最好的方法是使用 win32com,但我根本无法取得任何进展.

comobj.dll hosts a COM object (lets say, 'MainInteract') that I would like to use from Python. I can already use this object perfectly fine from IronPython, but due to other requirements I need to use it from regular Python. I believe the best method here is to use win32com, but I can't quite make any headway at all.

首先,一些可用的 IronPython 代码:

First, some working IronPython code:

import clr
import os
import sys

__dir__ = os.path.dirname(os.path.realpath(__file__))
sys.path.insert(0, __dir__)
sys.path.append(r"C:PathTocomobj.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 中尝试的代码:


And now the code that I attempted in regular 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:Python26libsite-packageswin32comclient\__init__.py", line 95, in Dispatch
    dispatch, userName = dynamic._GetGoodDispatchAndUserName(dispatch,userName,clsctx)
  File "C:Python26libsite-packageswin32comclientdynamic.py", line 104, in _GetGoodDispatchAndUserName
    return (_GetGoodDispatch(IDispatch, clsctx), userName)
  File "C:Python26libsite-packageswin32comclientdynamic.py", line 84, in _GetGoodDispatch
    IDispatch = pythoncom.CoCreateInstance(IDispatch, None, clsctx, pythoncom.IID_IDispatch)
 pywintypes.com_error: (-2147221164, 'Class not registered', None, None)

我也尝试过使用 TLB 的友好名称:

I have also attempted using the friendly name of the 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:Python26libsite-packageswin32comclient\__init__.py", line 95, in Dispatch
dispatch, userName = dynamic._GetGoodDispatchAndUserName(dispatch,userName,clsctx)
  File "C:Python26libsite-packageswin32comclientdynamic.py", line 104, in _GetGoodDispatchAndUserName
return (_GetGoodDispatch(IDispatch, clsctx), userName)
  File "C:Python26libsite-packageswin32comclientdynamic.py", line 84, in _GetGoodDispatch
    IDispatch = pythoncom.CoCreateInstance(IDispatch, None, clsctx, pythoncom.IID_IDispatch)
pywintypes.com_error: (-2147221005, 'Invalid class string', None, None)

事实上,我唯一的成功是:

In fact, the only success I've had was this:

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() 就可以了.

But i'm not sure how to go from there to getting an object. I think my problem is that I need to load the dll into my process and get it to register itself with my process's COM source, so I can properly CoCreateInstance / win32com.client.Dispatch() on it.

我还看到了激活上下文引用,尤其是在谈论无注册 COM"时,但通常在诸如如果您在 .manifest 文件中指定正确的内容,Windows 将为您创建上下文"这样的句子中.如果可能的话,我想避免使用清单文件,因为在与(封闭源)COM 对象 dll 相同的文件夹中需要一个文件,如果可以避免的话,我宁愿不删除该目录中的任何文件.

I have also seen Activation Contexts referenced, especially when talking about 'no registration COM', but typically in a sentences like "Windows will create a context for you if you specify the right stuff in your .manifest files". I'd like to avoid manifest files if possible, as one would be required in the same folder as the (closed source) COM object dll, and i'd rather not drop any files in that directory if I can avoid it.

感谢您的帮助.

推荐答案

有关包装 object-from-DLL 案例的有用实用程序模块以及其他情况,请参阅 https://gist.github.com/4219140

For a useful utility module that wraps the object-from-DLL case, as well as others, see 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 对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆