确保已终止COM连接启动的进程

时间:2015-05-13 20:55:23

标签: python python-2.7 com win32com minitab

我正在使用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_datathreading.Thread中运行multiprocessing.Process而没有运气。

修改

如果我的脚本只包含

from win32com.client import gencache
app = gencache.EnsureDispatch('Mtb.Application')

然后当我运行它时,我在任务管理器中看到Mtb.exe进程,但是一旦脚本退出,该进程就会被终止。所以相反,我的问题是,为什么这个COM对象在顶层和函数内声明是否重要?

3 个答案:

答案 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()