2010-06-24 10 views
11

Tôi đã ba mẩu mã mà tôi đang làm việc với tại thời điểm này:Tiếp cận đối tượng COM đăng ký từ trăn qua một TLB đăng ký

  • Một đóng ứng dụng mã nguồn (main.exe)
  • Một đóng nguồn VB COM đối tượng thực hiện như một dll (comobj.dll)
  • Mã mà tôi đang phát triển bằng Python

comobj.dll tổ chức một đối tượng COM (cho phép nói, 'MainInteract') mà tôi muốn sử dụng từ Python. Tôi đã có thể sử dụng đối tượng này hoàn toàn tốt đẹp từ IronPython, nhưng do các yêu cầu khác tôi cần phải sử dụng nó từ Python thông thường. Tôi tin rằng phương pháp tốt nhất ở đây là sử dụng win32com, nhưng tôi không thể thực hiện bất kỳ tiến bộ nào cả.

Thứ nhất, một số mã làm việc 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) 

Và bây giờ đoạn code mà tôi đã cố gắng bằng Python thường xuyên:

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

Tôi cũng đã cố gắng sử dụng tên thân thiện của 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) 

Thực ra, thành công duy nhất tôi có được là:

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) 

Nhưng tôi không chắc chắn cách đi từ đó để lấy vật thể. Tôi nghĩ rằng vấn đề của tôi là tôi cần phải tải dll vào quá trình của tôi và làm cho nó để đăng ký chính nó với nguồn COM của quá trình của tôi, vì vậy tôi có thể đúng CoCreateInstance/win32com.client.Dispatch() trên đó.

Tôi cũng thấy Activation Contexts được tham chiếu, đặc biệt khi nói về 'không có đăng ký COM', nhưng thường trong một câu như "Windows sẽ tạo ngữ cảnh cho bạn nếu bạn chỉ định đúng nội dung trong tệp .manifest của bạn". Tôi muốn tránh các tập tin manifest nếu có thể, như một trong những sẽ được yêu cầu trong cùng một thư mục như (đóng nguồn) COM đối tượng dll, và tôi không muốn thả bất kỳ tập tin trong thư mục đó nếu tôi có thể tránh nó.

Cảm ơn sự giúp đỡ.

+0

Tôi không biết câu trả lời ra khỏi đỉnh đầu của tôi, nhưng nếu bạn có thể làm điều đó với C++ sau đó bạn có thể khá trivially quấn đó. – ConcernedOfTunbridgeWells

+0

Vâng vâng, tôi cho rằng đó là một lựa chọn, nhưng tôi đã hy vọng để tránh nó. –

Trả lời

3

Đối với một mô-đun tiện ích hữu ích mà kết thúc tốt đẹp các trường hợp đối tượng từ-DLL, cũng như những người khác, xem 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

Đây là một phương pháp tôi nghĩ ra để tải một đối tượng COM từ một DLL. Nó được dựa trên rất nhiều đọc về COM, vv Tôi không chắc chắn 100% về các dòng cuối cùng, cụ thể là d =. Tôi nghĩ rằng chỉ hoạt động nếu IID_Dispatch được thông qua trong (mà bạn có thể xem nếu param mặc định).

Ngoài ra, tôi tin rằng mã này bị rò rỉ - cho một, DLL không bao giờ được tải (sử dụng ctypes.windll.kernel32.FreeLibraryW) và tôi tin rằng số lượng ref COM cho nhà máy lớp ban đầu bị tắt bởi một, và do đó không bao giờ được thả ra. Nhưng vẫn còn, điều này làm việc cho ứng dụng của tôi.

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

Đã thử nghiệm: giải pháp này hoạt động. Không biết nếu nó bị rò rỉ – yanjost

+0

Bạn có thể quan tâm đến https://gist.github.com/CBWhiz/4219140, nơi CoCreateInstanceFromDll được triển khai theo cùng một cách. –

10

Những gì tôi đã làm để truy cập vào thư viện kiểu Free Download Manager là như sau:

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 

Đoạn mã trên sẽ in URL của tải đầu tiên.

+0

Cảm ơn, Márcio Faustino! Tuyệt quá! Điều này làm việc rất tốt cho tôi! – ECC