【问题标题】:Access COM methods from Python从 Python 访问 COM 方法
【发布时间】:2018-08-05 18:37:32
【问题描述】:

我有一个旧的 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接口的Interface,因为该接口不是COM接口。在 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 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 接口的包装器
  • this article的方式可能适用。因为是日文,请使用谷歌翻译等阅读。

标签: python-3.x com


【解决方案1】:

感谢提供一些提示的人。实际上我无法修复 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 大多数(很多?)(一些?)标准 win32 API 可以通过 pywin32 以更直接的方式访问。
猜你喜欢
  • 1970-01-01
  • 2010-09-30
  • 2010-10-12
  • 2011-08-27
  • 1970-01-01
  • 1970-01-01
  • 2012-05-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多