I'm hoping someone can help me with being able to make a marshaled cross-process call into Excel from Python.
I have an Excel session initiated via Python that I know will be up and running when it needs to be accessed from a separate Python process. I have got everything working as desired using marshaling with CoMarshalInterfaceInStream()
and CoGetInterfaceAndReleaseStream()
calls from the pythoncom module, but I need repeat access to the stream (which I can only set up once in my case), and CoGetInterfaceAndReleaseStream()
allows once-only access to the interface.
I believe that what I would like to achieve can be done with CreateStreamOnHGlobal()
, CoMarshalInterface()
and CoUnmarshalInterface()
but am unable to get it working, almost certainly because I am not passing in the correct parameters.
Rather than describe in detail my main scenario, I have set up a simple example program as follows - obviously this takes place in the same process but one step at a time! The following snippet works fine:
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"
However, when I try the following:
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)
I get this error (which I imagine is related to the 3rd parameter) when making the call to pythoncom.CoMarshalInterface()
:
"ValueError: argument is not a COM object (got type=instance)"
Does anyone know how I can get this simple example working?
Thanks in advance
After much angst, I have managed to resolve the issue I was facing, and indeed subsequent ones which I will also describe.
First, I was correct in guessing that my initial problem was with the 3rd parameter in the call to pythoncom.CoMarshalInterface(). In fact, I should have been making a reference to the oleobj property of my excelApp variable:
pythoncom.CoMarshalInterface(myStream,
pythoncom.IID_IDispatch,
excelApp._oleobj_,
pythoncom.MSHCTX_LOCAL,
pythoncom.MSHLFLAGS_TABLESTRONG)
However, I then faced a different error message, this time in the call to pythoncom.CoUnmarshalInterface():
com_error: (-2147287010, 'A disk error occurred during a read operation.', None, None)
It turned out that this was due to the fact that the stream pointer needs to be reset prior to use, with the Seek() method:
myStream.Seek(0,0)
Finally, although most aspects were working correctly, I found that despite using Quit() on the marshalled Excel object and explicitly setting all variables to None prior to the end of the code, I was left with a zombie Excel process. This was despite the fact that pythoncom._GetInterfaceCount() was returning 0.
It turns out that I had to explicitly empty the stream I had created with a call to CoReleaseMarshalData() (having first reset the stream pointer again). So, the example code snippet in its entirety looks like this:
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()
I hope that this helps someone else out there to overcome the obstacles I faced!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With