使用pythoncom在Python进程间传递COM对象

5 投票
1 回答
2632 浏览
提问于 2025-04-18 01:20

我希望有人能帮我实现从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 个回答

11

经过一番折腾,我终于解决了我遇到的问题,还有后面的一些问题,我也会一并说明。

首先,我猜对了,最初的问题确实出在调用 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()

希望这些能帮助到其他人,克服我曾经遇到的障碍!

撰写回答