我正在使用Python的win32com
库自动执行Minitab 17,虽然所有命令都正确执行,但是当我的脚本结束时,我似乎无法通过Minitab进程启动进程。我的结构看起来像
from myapi import get_data
import pythoncom
from win32com.client import gencache
def process_data(data):
# In case of threading
pythoncom.CoInitialize()
app = gencache.EnsureDispatch('Mtb.Application')
try:
# do some processing
pass
finally:
# App-specific command that is supposed to close the software
app.Quit()
# Ensure the object is released
del mtb
# In case of threading
pythoncom.CoUninitialize()
def main():
data = get_data()
process_data(data)
if __name__ == '__main__':
main()
我没有收到任何异常或打印错误消息,Mtb.exe
进程仍然列在任务管理器中。更令人沮丧的是,如果我在IPython会话中运行以下内容:
>>> from win32com.client import gencache
>>> app = gencache.EnsureDispatch('Mtb.Application')
>>> ^D
Minitab流程立即关闭。我在正常的python
交互式会话中观察到相同的行为。 为什么在交互式会话中运行而不是在独立脚本中运行时,进程是否正确关闭?在我的脚本中没有执行哪些不同的操作?
我还尝试在process_data
和threading.Thread
中运行multiprocessing.Process
而没有运气。
修改
如果我的脚本只包含
from win32com.client import gencache
app = gencache.EnsureDispatch('Mtb.Application')
然后当我运行它时,我在任务管理器中看到Mtb.exe
进程,但是一旦脚本退出,该进程就会被终止。所以相反,我的问题是,为什么这个COM对象在顶层和函数内声明是否重要?
答案 0 :(得分:1)
我没有minitab因此我无法验证但是在调用app.Quit之后设置app = None尝试强制关闭COM服务器? Python使用ref计数来管理对象生命周期,因此假设没有其他refs应用程序,那么将其设置为none应该导致它立即完成。我已经看到导致类似问题。你不应该需要弱引用,其他的东西正在发生。根据您的回答,以下内容应该有效:
def process_data(mtb, data):
try:
mtb.do_something(data)
finally:
mtb.Quit()
def main(mtb):
data = get_data()
process_data(mtb, data)
if __name__ == '__main__':
pythoncom.CoInitialize()
mtb = gencache.EnsureDispatch('Mtb.Application')
main(mtb)
mtb.Quit()
mtb = None
pythoncom.CoUninitialize()
答案 1 :(得分:0)
问题在于垃圾收集器可以清理对底层IUnknown
对象(所有COM对象的基类型)的引用,并且没有gc执行它的工作,该进程保持活动状态。我通过使用weakref
模块立即将COM对象包装在weakref中来解决问题,因此可以更容易地引用它:
from myapi import get_data
import weakref
from win32com.client import gencache
import pythoncom
def process_data(mtb_ref, data):
try:
mtb_ref().do_something(data)
finally:
mtb_ref().Quit()
def main(mtb_ref):
data = get_data()
process_data(mtb_ref, data)
if __name__ == '__main__':
pythoncom.CoInitialize()
mtb_ref = weakref.ref(gencache.EnsureDispatch('Mtb.Application'))
main(mtb_ref)
pythoncom.CoUninitialize()
我不确定我完全理解为什么会有所不同,但我相信这是因为从来没有直接引用该对象,只有弱引用,所以使用COM对象的所有函数只是间接地这样做,允许GC知道可以更快地收集对象。无论出于何种原因,它仍然需要在模块的顶层创建,但这至少使我能够编写更多可以干净利用的可重用代码。
答案 2 :(得分:0)
在pythoncom.CoUninitialize()之后我仍然看到进程
对我来说它有帮助(based):
from comtypes.automation import IDispatch
from ctypes import c_void_p, cast, POINTER, byref
def release_reference(self, obj):
logger.debug("release com object")
oleobj = obj._oleobj_
addr = int(repr(oleobj).split()[-1][2:-1], 16)
pointer = POINTER(IDispatch)()
cast(byref(pointer), POINTER(c_void_p))[0] = addr
pointer.Release()