Python ctypes和使用COM对象的DLL

5
在Windows系统下,我试图使用一个用C++编写的第三方DLL库(SomeLib.dll),并通过ctypes在Python 2.7中调用它。对于一些功能,该库使用另一个COM DLL(SomeCOMlib.dll),而该COM DLL本身使用其他DLL(LibA.dll)。
请注意,这不是关于直接从Python使用COM DLL,而是关于从Python使用使用了COM DLL的DLL。
为了使与Python的集成更加容易,我将我想要使用的调用分组到我的新DLL库(MyLib.dll)中,同样使用C++编写,只是为了方便使用ctypes进行调用(使用extern "C"并针对特定场景定制函数)。实质上,我的库暴露了两个函数:doSomethingSimple(), doSomethingWithCOMobj()(都返回void,没有参数)。
“有效”的依赖关系层次结构如下:
MyLib.dll
  SomeLib.dll
    SomeCOMlib.dll
      LibA.dll

我可以写一个简单的C++控制台应用程序(Visual C++),使用并进行这两个连续调用而没有问题。

使用Python/ctypes,第一次调用正常工作,但使用COM的调用会抛出WindowsError: [Error -529697949] Windows Error 0xE06D7363。从库的其他行为中可以看出,问题确切地出现在进行COM调用的地方。

(如果缺少LibA.dll,简单测试的C++应用程序也在大致相同的位置失败,但我不确定是否相关。)

我使用Dependency Walker查看了依赖关系层次结构。虽然明显需要它,但是SomeCOMlib.dll没有列为SomeLib.dll的依赖项,并且LibA.dll没有列为SomeCOMlib.dll的依赖项,在运行时也明显需要。

我从命令行运行一切,在这些DLL所在的目录中运行(C++示例可执行文件工作正常)。我尝试强制包含该目录的PATH,还将DLL复制到我猜测可能会被拾取的各种地方(C:\Windows\System32C:\Python27\DLLs),但没有成功。已使用regasm.exe注册SomeCOMlib.dll

什么原因导致从普通的C++应用程序和Python的ctypes使用此DLL时,在其自己使用COM机制(以及可能在那里后续加载其他DLL)方面存在差异?

哪些步骤可以提供比Python更多的信息,至少能够进一步调查问题,而不是只有Windows Error 0xE06D7363

Python代码如下:

import ctypes
myDll = ctypes.WinDLL("MyLib.dll")
myDll.doSomethingSimple()
myDll.doSomethingWithCOMobj() # This statement throws the Windows Error

(连接到MyLib.dll的测试 C++ 独立应用程序在 main 中进行完全相同的调用。)

3个回答

5
当您需要一个进程内的COM对象时,您不会直接链接到实现DLL。通常使用CoCreateInstance/CoCreateInstanceEx,它将为您加载DLL。
查找通过应用程序清单和其依赖的程序集清单进行。这是为了支持无需注册的COM
如果没有应用程序清单或者没有任何依赖程序集清单在comClass XML元素中声明您的类,查找将默认为注册表,它将检查HKEY_CLASSES_ROOT\CLSID1是否有一个名为{<your-CLSID>}的子键,并且其中包含一个声明DLL的InProcServer32子键。

这就解释了为什么SomeCOMlib.dll不出现为依赖项。但它并不能解释为什么LibA.dll不出现为它的依赖项,可能是因为它是动态加载的。如果您在Dependency Walker中对应用程序进行分析,您将在底部窗格中看到LoadLibrary调用的日志。要对其进行分析,请在Dependency Walker中打开您的Python可执行文件,然后转到菜单选项Profile->Start profiling...,设置参数以运行您的.py文件,然后单击Ok。

0xE06D7363异常代码是Visual C++异常代码。您应该检查doSomethingWithCOMobj的源代码。要进行调试,请使用您喜欢的工具(Visual C ++,WinDbg等),打开Python的可执行文件,设置参数以运行您的.py文件,并在运行应用程序之前在函数的第一条语句上启用断点。然后运行它并逐步执行每个指令。
很难猜测您的本地C ++应用程序和Python之间的差异,但可能是Python和doSomethingWithCOMobj使用不同的COM初始化参数,或者您没有声明__stdcall(虽然它是一个void函数,但这不应该有影响),或者它尝试写入stdout,而您正在使用不是控制台应用程序的pythonw.exe等。

1. HKEY_CLASSES_ROOTHKEY_CURRENT_USER\Software\ClassesHKEY_LOCAL_MACHINE\Software\Classes 的混合。


谢谢,问题在于我的C++应用程序和Python应用程序都没有进行任何COM初始化。如果发生了什么事情,它是自动的(并且当使用Python时不会显示发生)。我只是在使用这个使用了那个COM DLL的第三方DLL。 - Bruno
@Bruno,听起来很奇怪。这可能意味着你的C++实际上正在调用CoInitialize[Ex]/OleInitialize,或者MyLib.dll或SomeLib.dll在Python应用程序调用CoInitializeExCOINIT_MULTITHREADED之后使用COINIT_APARTMENTTHREADEDCoInitializeOleInitialize的默认值)。我认为DLLs不应该自己调用这些函数。 - acelent
@Bruno,关于实际的错误,由于它看起来是一个C++异常代码,我建议你查看并调试MyLib.dll。如果你有Visual C++,你可以通过运行一个加载和使用该DLL的应用程序(比如你的Python应用程序)来进行调试。 - acelent
你知道在ctypes中如何实现这个吗?我正在尝试使用IFileOperation,但不确定该怎么做。链接 - Jay
查看shobjidl_core.h中的实际接口定义,因为文档中的方法顺序是按字母顺序排列的,实际的类/结构顺序很重要。特别注意输出和输入/输出参数。Shell对象期望存在于STA(单线程公寓)中。 - acelent

2

您可以使用类似于“processexplorer”这样的工具,查看是否将所有必需的库加载到正在运行的进程中。

  1. 您是否在Python代码或C++代码中初始化了COM运行时?C++代码和Python/ctypes之间的一个区别是,当加载dll时,C++代码会调用dll的DllMain()函数,而Python/ctypes不会这样做,因为dll是完全动态加载的。

谢谢。我在我的Python或C++代码中没有初始化COM运行时(至少不是显式地)。关于这一切的一切都是从第三方DLL中完成的,我的代码并不知道任何信息。Python代码是否也需要进行一些COM初始化呢? - Bruno
不需要,在我所知道的情况下。至少如果你的代码在主线程中运行。 - theller
这些答案并没有完全解决问题,但它们对于找到正确的方向很有用。我会接受这个答案,除非后来有人提出了确切的解决方案... - Bruno
如何在ctypes中初始化com对象?我正在尝试使用IFileOperation,但遇到了困难。 - Jay

1
我猜你可能遇到了和我一样的问题,但我不确定,因为与此情况不同,我对使用的dll一无所知。虽然6年过去了,我不知道你是否还能测试,但我想知道导入pythoncom是否可以解决这个问题。
import ctypes
import pythoncom
myDll = ctypes.WinDLL("MyLib.dll")
myDll.doSomethingSimple()
myDll.doSomethingWithCOMobj() # This statement throws the Windows Error

谢谢。很抱歉,我已经没有尝试这个的位置了。那似乎是一个合理的解决方案。 - Bruno

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