使用pythoncom在Python进程间传递COM对象
我希望有人能帮我实现从Python中向Excel进行跨进程调用。
我通过Python启动了一个Excel会话,我知道在需要从另一个Python进程访问时,它会一直运行。我已经通过使用CoMarshalInterfaceInStream()
和CoGetInterfaceAndReleaseStream()
这两个函数,成功地让一切按预期工作,但我需要重复访问这个流(在我的情况下只能设置一次),而CoGetInterfaceAndReleaseStream()
只允许一次性访问接口。
我认为我想要实现的功能可以通过CreateStreamOnHGlobal()
、CoMarshalInterface()
和CoUnmarshalInterface()
来完成,但我无法让它工作,几乎可以肯定是因为我没有传递正确的参数。
与其详细描述我的主要场景,我设置了一个简单的示例程序如下——显然这发生在同一个进程中,但一步一步来!以下代码片段运行得很好:
import win32com.client
import pythoncom
excelApp = win32com.client.DispatchEx("Excel.Application")
marshalledExcelApp = pythoncom.CoMarshalInterThreadInterfaceInStream(pythoncom.IID_IDispatch, excelApp)
xlApp = win32com.client.Dispatch(
pythoncom.CoGetInterfaceAndReleaseStream(marshalledExcelApp, pythoncom.IID_IDispatch))
xlWb = xlApp.Workbooks.Add()
xlWs = xlWb.Worksheets.Add()
xlWs.Range("A1").Value = "AAA"
然而,当我尝试以下代码时:
import win32com.client
import pythoncom
excelApp = win32com.client.DispatchEx("Excel.Application")
myStream = pythoncom.CreateStreamOnHGlobal()
pythoncom.CoMarshalInterface(myStream,
pythoncom.IID_IDispatch,
excelApp,
pythoncom.MSHCTX_LOCAL,
pythoncom.MSHLFLAGS_TABLESTRONG)
myUnmarshaledInterface = pythoncom.CoUnmarshalInterface(myStream, pythoncom.IID_IDispatch)
在调用pythoncom.CoMarshalInterface()
时,我遇到了这个错误(我想这与第三个参数有关):
"ValueError: argument is not a COM object (got type=instance)"
有没有人知道我如何让这个简单的示例工作?
提前谢谢大家!
1 个回答
经过一番折腾,我终于解决了我遇到的问题,还有后面的一些问题,我也会一并说明。
首先,我猜对了,最初的问题确实出在调用 pythoncom.CoMarshalInterface() 时的第三个参数。实际上,我应该引用我 excelApp 变量的 oleobj 属性:
pythoncom.CoMarshalInterface(myStream,
pythoncom.IID_IDispatch,
excelApp._oleobj_,
pythoncom.MSHCTX_LOCAL,
pythoncom.MSHLFLAGS_TABLESTRONG)
不过,接下来我又遇到了一个不同的错误信息,这次是在调用 pythoncom.CoUnmarshalInterface() 时出现的:
com_error: (-2147287010, 'A disk error occurred during a read operation.', None, None)
结果发现,这是因为在使用之前需要重置流指针,可以通过 Seek() 方法来实现:
myStream.Seek(0,0)
最后,虽然大部分功能都正常,但我发现即使在对已封送的 Excel 对象调用 Quit(),并在代码结束前将所有变量设置为 None,我仍然留下了一个“僵尸” Excel 进程。尽管 pythoncom._GetInterfaceCount() 返回的是 0。
原来,我还需要明确地清空我创建的流,方法是调用 CoReleaseMarshalData()(在此之前要再次重置流指针)。所以,完整的示例代码看起来是这样的:
import win32com.client
import pythoncom
pythoncom.CoInitialize()
excelApp = win32com.client.DispatchEx("Excel.Application")
myStream = pythoncom.CreateStreamOnHGlobal()
pythoncom.CoMarshalInterface(myStream,
pythoncom.IID_IDispatch,
excelApp._oleobj_,
pythoncom.MSHCTX_LOCAL,
pythoncom.MSHLFLAGS_TABLESTRONG)
excelApp = None
myStream.Seek(0,0)
myUnmarshaledInterface = pythoncom.CoUnmarshalInterface(myStream, pythoncom.IID_IDispatch)
unmarshalledExcelApp = win32com.client.Dispatch(myUnmarshaledInterface)
# Do some stuff in Excel in order to prove that marshalling has worked.
unmarshalledExcelApp.Visible = True
xlWbs = unmarshalledExcelApp.Workbooks
xlWb = xlWbs.Add()
xlWss = xlWb.Worksheets
xlWs = xlWss.Add()
xlRange = xlWs.Range("A1")
xlRange.Value = "AAA"
unmarshalledExcelApp.Quit()
# Clear the stream now that we have finished
myStream.Seek(0,0)
pythoncom.CoReleaseMarshalData(myStream)
xlRange = None
xlWs = None
xlWss = None
xlWb = None
xlWbs = None
myUnmarshaledInterface = None
unmarshalledExcelApp = None
myStream = None
pythoncom.CoUninitialize()
希望这些能帮助到其他人,克服我曾经遇到的障碍!