从Python中访问COM方法

4

我有一份老旧的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实现,因此我无法查询I函数接口的QueryInterface,因为该接口不是COM接口。在Windows注册表中,我仅找到对象的CLSID和DLL的引用作为InprocServer32。

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

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 ++创建一个包装器,以呈现完整的COM接口。 - M.M
这篇文章的方法可能适用。因为它是日语,请使用Google翻译等工具进行阅读。 - kunif
1个回答

8

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


看起来很累:/ 我特别卡在IFileOperation,即使是一个“标准”的win-api com函数,也需要在Python中完成所有这些吗? - Jay
@Jay,许多标准Win32 API可以通过pywin32以更直接的方式进行访问。 - Dustin Wyatt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接