代码之家  ›  专栏  ›  技术社区  ›  Toponimo

从Python访问COM方法

  •  3
  • Toponimo  · 技术社区  · 6 年前

    我有一个旧的Windows DLL,没有源代码,它实现了一个实用函数表。多年前,计划将其转换为COM对象,以便实现IUnknown接口。要使用此DLL,有一个头文件(简化):

    interface IFunctions : public IUnknown
    {
        virtual int function1(int p1, int p2) = 0;
        virtual void function2(int p1) = 0;
        // and the likes ...
    }
    

    但没有为IFunctions接口定义CLSID。最终,头文件中的接口定义不符合COM标准。

    从C++加载DLL时,可以使用

    CoCreateInstance(clsid, 0, CLSCTX_INPROC_SERVER, clsid, ptr);
    

    使用“ptr”中的一些指针算法,我找到了Function1()等的地址。由于它有效,没有完成任何COM实现,因此我无法查询IFunctions接口,因为该接口不是COM接口。在Windows注册表中,我只找到对象的CLSID和对DLL的引用,因为它在ProcServer32中。

    我在Python方面没有太多经验,但我需要使用Python中的这个DLL,可能需要使用ctypes和comtypes。我可以使用(注册表中的CLSID)加载DLL

    unk = CreateObject('{11111111-2222-3333-4444-555555555555}', clsctx=comtypes.CLSCTX_INPROC_SERVER)
    

    我知道在COM对象function1()的VTable中,address正好位于QueryInterface()、AddRef()、Release()之后,但我找不到实现如下类的解决方案:

    class DllFunction:
        # not necessary, but for completeness ...
        def QueryInterface(self, interface, iid=None):
            return unk.QueryInterface(comtypes.IUnknown)
        def AddRef(slef):
            return unk.AddRef()
        def Release(self):
            return unk.Release()
        # Functions I actually need to call from Python
        def Function1(self, p1, p2):
            # what to do ??
        def Function2(self, p1):
        # etc.
    

    我想用Python实现这个解决方案,以避免在C++中开发扩展模块。

    谢谢你的帮助。

    1 回复  |  直到 6 年前
        1
  •  6
  •   Toponimo    6 年前

    感谢世卫组织提供了一些提示。实际上,我无法修复DLL,因为我没有源代码。用C++包装它是一种选择,但用C开发包装Python模块听起来更好。 我的计划是只使用Python,可能没有额外的模块,所以我设法只使用ctypes来解决这个问题。以下代码显示了解决方案。它可以工作,但需要一些改进(错误检查等)。

    '''
        Simple example of how to use the DLL from Python on Win32.
    
        We need only ctypes.
    '''
    import ctypes
    from ctypes import *
    '''
        We need a class to mirror GUID structure
    '''
    class GUID(Structure):
        _fields_ = [("Data1", c_ulong),
                    ("Data2", c_ushort),
                    ("Data3", c_ushort),
                    ("Data4", c_ubyte * 8)]
    
    if __name__ == "__main__":
        '''
            COM APIs to activate/deactivate COM environment and load the COM object
        '''
        ole32=WinDLL('Ole32.dll')
        CoInitialize = ole32.CoInitialize
        CoUninitialize = ole32.CoUninitialize
        CoCreateInstance = ole32.CoCreateInstance
        '''
            COM environment initialization
        '''
        rc = CoInitialize(None)
        '''
            To use CoCreate Instance in C (not C++):
                void * driver = NULL;
                rc = CoCreateInstance(&IID_Driver,      // CLSID of the COM object
                               0,                       // no aggregation
                               CLSCTX_INPROC_SERVER,    // CLSCTX_INPROC_SERVER = 1
                               &IID_Driver,             // CLSID of the required interface
                               (void**)&driver);        // result
            In Python it is:
        '''
        clsid = GUID(0x11111111, 0x2222, 0x3333, 
                   (0x44, 0x44, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55))
        drv = c_void_p(None)
        rc = CoCreateInstance(byref(clsid), 0, 1, byref(clsid), byref(drv))
        '''
            Pointers manipulation. Short form:
            function = cast( c_void_p( cast(drv, POINTER(c_void_p))[0] ), POINTER(c_void_p))
        '''
        VTable = cast(drv, POINTER(c_void_p))
        wk = c_void_p(VTable[0])
        function = cast(wk, POINTER(c_void_p))
        #print('VTbale address: ', hex(VTable[0]))
        #print('QueryInterface address: ', hex(function[0]))
        #print('AddRef address: ', hex(function[1]))
        #print('Release address: ', hex(function[2]))
        '''
            To define functions from their addresses we first need to define their WINFUNCTYPE.
            In C we call QueryInterface:
                HRESULT rc = driver->lpVtbl->QueryInterface(driver, &IID_IUnknown, (void**)&iUnk);
            So we need a long (HRESULT) return value and three pointers. It would be better to be
            more accurate in pointer types, but ... it works!
            We can use whatever names we want for function types and functions
        '''
        QueryInterfaceType = WINFUNCTYPE(c_long, c_void_p, c_void_p, c_void_p)
        QueryInterface = QueryInterfaceType(function[0])
        AddRefType = WINFUNCTYPE(c_ulong, c_void_p)
        AddRef = AddRefType(function[1])
        ReleaseType = WINFUNCTYPE(c_ulong, c_void_p)
        Release = ReleaseType(function[2])
        '''
            The same for other functions, but library functions do not want 'this':
                long rc = driver->lpVtbl->init(0);
        '''
        doThisType = WINFUNCTYPE(c_long, c_void_p)
        doThis=doThisType(function[3])
    
        getNameType = WINFUNCTYPE(c_int, c_char_p)
        getName = getNameType(function[4])
        getName.restype = None      # to have None since function is void
    
        getVersionType = WINFUNCTYPE(c_long)
        getVersion = getVersionType(function[5])
    
        getMessageType = WINFUNCTYPE(c_int, c_char_p)
        getMessage = getMessageType(function[6])
        getMessage.restype = None       # to have None since function is void
        '''
            Now we can use functions in plain Python
        '''
        rc = doThis(0)
        print(rc)
    
        name = create_string_buffer(128)
        rc = getName(name)
        print(rc)
        print(name.value)
    
        ver = getVersion()
        print(ver)
    
        msg = create_string_buffer(256)
        rc = getMessage(msg)
        print(rc)
        print(msg.value)
        '''
            Unload DLL and reset COM environment
        '''
        rc = Release(drv)
        rc = CoUninitialize()
    
        print("Done!")
    

    我希望这个例子对某些人有用。它可以用来包装没有comtypes的COM对象,对我来说,还可以阐明ctypes是如何工作的。