从 Python 访问 COM 方法

Access COM methods from Python

我有一个旧的 Windows DLL,没有源代码,它实现了 table 实用功能。多年前,计划将其转换为 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 接口查询接口,因为接口不是通讯接口。在 Windows 注册表中,我只找到对象的 CLSID 和对 DLL 的引用,因为它是 InprocServer32。

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

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

我知道在 COM 对象的 VTable 中,function1() 地址就在 QueryInterface()、AddRef()、Release() 之后,但我找不到实现 class 的解决方案,例如:

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 的工作原理。