背景
迫于集团的强制要求,Windows下的客户端需要接入一个安全组件。启动时主程序通过这个安全组件加载其他动态库,在加载动态库时则会检查文件的签名和签发人,确保加载的动态库没有被篡改。
安全组件提供了动态库、头文件及msvc调用的例子。本以为用Python的ctypes直接调用动态库导出的函数就能搞定。结果发现事情并没有那么简单。动态库只导出了根据IID获取COM对象的接口。实际的功能函数都在对应的COM对象的成员函数中。而头文件则对这一系列操作做了封装,将获取COM对象,调用相关成员函数等一系列操作,封装在一个内联函数中。
对于用C/C++开发的程序,直接调用include头文件,调用这些内联函数就行了。但我们的客户端是使用Python开发的,没法这么做。最简单的方法应该是再实现一个动态库,里面实现调用这些内联函数的函数,并把自己实现的函数导出,在Python中通过ctypes去调用。然而这么套娃,总感觉很奇怪。于是就想着,能不能在Python中直接获取并使用这些COM对象(作死开始)。
pythoncom
先尝试了如下代码:
1 | import pythoncom |
执行后报错:
1 | TypeError: There is no interface object registered that supports this IID |
一通Google之后,大概找到了问题的原因。pythoncom需要COM对象继承并实现IDispatch的相关接口,否则就不知道COM对象实现的函数和返回值类型。很明显,安全组件提供的COM组件并没有继承和实现IDipatch接口。
通过虚函数表调用
仔细想想,COM对象,本质上也是一个C++对象,应该能通过获取虚表中各个函数的函数指针,直接调用成员函数。
再次Google一番之后,找到了一些相关的资料。The layout of a COM object中提到:
- COM对象的开头为指向虚函数表的指针。
- 虚表中存放着调用方式为__stdcall的函数指针,第一个参数为对象本身,之后为函数入参。
- 同一个类中的函数按照声明的先后顺序排列。基类的函数指针在派生类之前。
- 如果没有出现了菱形继承,开头只有一个虚表指针。否则,开头会出现多个虚表指针。需要根据被调用函数所在的基类,去判断需要使用的虚表。
实现的代码为
1 | from ctypes import addressof, cast, c_long, c_void_p, POINTER, WINFUNCTYPE |
其他
平心而论,通过这种方式使用COM对象,可维护性极差。如果可以选,类似场景下,还是建议使用背景中提到的方案。