1

我正在使用 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在 athreading.Thread和 a 中运行,multiprocessing.Process但没有运气。

编辑:

如果我有一个脚本只包含

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

然后当我运行它时,我会Mtb.exe在任务管理器中看到该进程,但是一旦脚本退出,该进程就会被杀死。因此,我的问题是,如果这个 COM 对象是在顶层声明还是在函数内部声明,为什么这很重要?

4

3 回答 3

2

我没有 minitab,所以我无法验证,但尝试通过在调用 app.Quit 之后设置 app = None 来强制关闭 COM 服务器?Python 使用引用计数来管理对象生命周期,因此假设应用程序没有其他引用,那么将其设置为 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()
于 2015-05-18T03:45:35.860 回答
0

问题是垃圾收集器可以清理对底层IUnknown对象(所有 COM 对象的基本类型)的引用,并且没有 gc 完成它的工作,进程保持活动状态。我通过使用weakref模块立即将 COM 对象包装在弱引用中来解决问题,这样它就可以更容易地被引用:

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知道可以更快地收集对象。无论出于何种原因,它仍然需要在模块的顶层创建,但这至少使我有可能编写更多可重用的代码并干净地退出。

于 2015-05-18T14:04:22.390 回答
0

在 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()
于 2017-05-02T08:59:46.007 回答