从Python访问COM方法

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从Python访问COM方法相关的知识,希望对你有一定的参考价值。

我有一个旧的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'中的一些指针算法,我找到了funcion1()等的地址。由于它工作,没有完成完整的COM实现,所以我不能用于IFunctions接口的QueryInterface,因为接口不是COM接口。在Windows注册表中,我只找到对象的CLSID和对InprocServer32的DLL的引用。

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

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

我知道在COM对象的VTable中,function1()地址就在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 ++中开发扩展模块。

谢谢你的帮助。

答案

感谢谁提供了一些提示。实际上我无法修复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的工作原理。

以上是关于从Python访问COM方法的主要内容,如果未能解决你的问题,请参考以下文章

常用python日期日志获取内容循环的代码片段

从 recyclerview 的适配器访问父片段方法

Python readlines Api从串口访问时需要很长时间

从片段访问父活动的数据

从子片段访问父片段方法

从 ViewPager Activity 访问 Fragment 的方法