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

4
我希望有人能够帮助我,让我能够从Python进程中对Excel进行马歇尔跨进程调用。
我已经通过使用pythoncom模块的CoMarshalInterfaceInStream()和CoGetInterfaceAndReleaseStream()调用来使Excel会话在Python中初始化,并且当需要从另一个Python进程访问时将保持运行。但是,我需要重复访问流(在我的情况下只能设置一次),而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) 

最后,虽然大多数方面都正常工作,但我发现尽管在marshalled 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()

我希望这能帮助到其他人克服我曾经面临的障碍!


有没有任何“官方”的文档可以解释这个问题? - Alexandru Aliu
这本书是一个很好的概述:https://thrysoee.dk/InsideCOM+/。在其中,您还可以看到,因为您在编组时指定了`MSHLFLAGS_TABLESTRONG`,所以只需要使用`CoUnmarshalInterface`。 - Chronial

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接