Marshaling COM objects between Python processes using pythoncom
Asked Answered
E

1

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

Epigrammatize answered 4/4, 2014 at 13:3 Comment(0)
E
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!

Epigrammatize answered 11/4, 2014 at 13:28 Comment(3)
Is there any kind of "official" documentation which explains this ?Comeaux
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.Fussbudget
superb materialReactionary

© 2022 - 2024 — McMap. All rights reserved.