5

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

1 Answer 1

11

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!

Sign up to request clarification or add additional context in comments.

3 Comments

Is there any kind of "official" documentation which explains this ?
This book is a good overview: thrysoee.dk/InsideCOM+. In there, you can also see that you only need to use CoUnmarshalInterface because you specified MSHLFLAGS_TABLESTRONG when marshalling.
superb material

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.