I'm automating Minitab 17 using Python's win32com
library, and while all of commands execute correctly, I can't seem to get the process started by the Minitab process to exit when my script ends. My structure looks like
我正在使用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()
I don't get any exceptions raised or error messages printed, the Mtb.exe
process is still listed in task manager. Even more frustrating is if I run the following in an IPython session:
我没有收到任何异常或打印错误消息,Mtb.exe进程仍然列在任务管理器中。更令人沮丧的是,如果我在IPython会话中运行以下内容:
>>> from win32com.client import gencache
>>> app = gencache.EnsureDispatch('Mtb.Application')
>>> ^D
The Minitab process is closed immediately. I observe the same behavior in a normal python
interactive session.
Why would the process get closed correctly when running in an interactive session but not in a standalone script? What is done differently there that isn't being performed in my script?
Minitab进程立即关闭。我在普通的python交互式会话中观察到相同的行为。为什么在交互式会话中运行而不是在独立脚本中运行时,进程是否正确关闭?在我的脚本中没有执行哪些不同的操作?
I've also tried running process_data
in a threading.Thread
and in a multiprocessing.Process
with no luck.
我也尝试在threading.Thread和多处理过程中运行process_data。没有运气。
EDIT:
If I have a script containing nothing but
如果我有一个只包含脚本的脚本
from win32com.client import gencache
app = gencache.EnsureDispatch('Mtb.Application')
then when I run it I see the Mtb.exe
process in task manager, but once the script exits the process is killed. So instead my question is why does it matter if this COM object is declared at top-level vs. inside a function?
然后当我运行它时,我在任务管理器中看到了Mtb.exe进程,但是一旦脚本退出,该进程就会被终止。所以相反,我的问题是,为什么这个COM对象在*和函数内声明是否重要?
3 个解决方案
#1
I don't have minitab so I can't verify but try forcing a shutdown of COM server by setting app = None just after the call to app.Quit? Python uses ref counting to manage object life cycle, so assuming there are no other refs to app then setting it to none should cause it to be finalized immediately. I have seen that cause similar issues. You should not need weak reference, something else is going on. The following, based on your answer, should work:
我没有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()
#2
The problem was that the the garbage collector could clean up the reference to the underlying IUnknown
object (the base type for all COM objects), and without the gc doing it's job the process stayed alive. I solve the problem by using the weakref
module to immediately wrap the COM object in a weakref so it could be more easily deferenced:
问题是垃圾收集器可以清理对底层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()
I'm not sure I understand fully why this makes a difference, but I believe it's because there's never a direct reference to the object, only a weak reference, so all the functions that use the COM object only do so indirectly, allowing the GC to know that the object can be collected sooner. For whatever reason it still needs to be created at the top level of the module, but this at least makes it possible for me to write more reusable code that cleanly exits.
我不确定我完全理解为什么会有所不同,但我相信这是因为从来没有直接引用该对象,只有弱引用,所以使用COM对象的所有函数只是间接地这样做,允许GC知道可以更快地收集对象。无论出于何种原因,它仍然需要在模块的顶层创建,但这至少使我能够编写更干净的退出代码。
#3
after pythoncom.CoUninitialize() i still see process
for me it help (based):
在pythoncom.CoUninitialize()之后,我仍然看到我的过程帮助(基于):
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()
#1
I don't have minitab so I can't verify but try forcing a shutdown of COM server by setting app = None just after the call to app.Quit? Python uses ref counting to manage object life cycle, so assuming there are no other refs to app then setting it to none should cause it to be finalized immediately. I have seen that cause similar issues. You should not need weak reference, something else is going on. The following, based on your answer, should work:
我没有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()
#2
The problem was that the the garbage collector could clean up the reference to the underlying IUnknown
object (the base type for all COM objects), and without the gc doing it's job the process stayed alive. I solve the problem by using the weakref
module to immediately wrap the COM object in a weakref so it could be more easily deferenced:
问题是垃圾收集器可以清理对底层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()
I'm not sure I understand fully why this makes a difference, but I believe it's because there's never a direct reference to the object, only a weak reference, so all the functions that use the COM object only do so indirectly, allowing the GC to know that the object can be collected sooner. For whatever reason it still needs to be created at the top level of the module, but this at least makes it possible for me to write more reusable code that cleanly exits.
我不确定我完全理解为什么会有所不同,但我相信这是因为从来没有直接引用该对象,只有弱引用,所以使用COM对象的所有函数只是间接地这样做,允许GC知道可以更快地收集对象。无论出于何种原因,它仍然需要在模块的顶层创建,但这至少使我能够编写更干净的退出代码。
#3
after pythoncom.CoUninitialize() i still see process
for me it help (based):
在pythoncom.CoUninitialize()之后,我仍然看到我的过程帮助(基于):
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()