As I had mentioned in a previous post, we have added support in IronPython for accessing OLE Automation objects using the IDispatch interface, without having to rely on interop assemblies. This was enabled with the "ipy.exe -X:PreferComDispatch" command-line until IronPython 2.0 Beta 3. In Beta 4, this behaviour is on by default. This makes IronPython's support for OLE Automation more script-friendly akin to VBScript and JScript.
In IronPython 1.X, trying to use Excel would cause IronPython to generate the inteorp assembly on the fly, which would take more than minute, before resulting in an exception. If you explictly added a reference to Microsoft.Office.Interop.Excel.dll, then things get back on track. However, note that not all machines with Office installed will necessarily have the primary interop assemblies (PIAs) installed in the GAC, as explained in this link.
c:\IronPython-1.1.2>ipy.exe -vIronPython 1.1.2 (1.1.2) on .NET 2.0.50727.1433Copyright (c) Microsoft Corporation. All rights reserved.>>> import System>>> t = System.Type.GetTypeFromProgID("Excel.Application")>>> excel = System.Activator.CreateInstance(t)>>> wb = excel.Workbooks.Add() # This will take a long timeGenerating Interop assembly for 000208d5-0000-0000-c000-000000000046Traceback (most recent call last): File , line 0, in <stdin>##16SystemError: Could not load type 'Excel._Application' from assembly 'Excel, Version=1.6.0.0, Culture=neutral, PublicKeyToken=null'.>>>>>> import clr>>> clr.AddReference("Microsoft.Office.Interop.Excel")>>> wb = excel.Workbooks.Add()>>>
In IronPython 2.0 Beta 3, things work much better, but as can be seen from the type of "wb", it still relies on the PIA. If the PIA is not installed or registered on the machine, IronPython would try to generate the interop assembly on the fly, again taking a very long time.
c:\IronPython-2.0B3>ipy.exe -vIronPython 2.0 Beta (2.0.0.3000) on .NET 2.0.50727.1433Type "help", "copyright", "credits" or "license" for more information.>>> import System>>> t = System.Type.GetTypeFromProgID("Excel.Application")>>> excel = System.Activator.CreateInstance(t)>>> wb = excel.Workbooks.Add()>>> wb<Microsoft.Office.Interop.Excel.WorkbookClass object at 0x000000000000002B>>>> wb.GetType().Assembly.CodeBase'file:///C:/Windows/assembly/GAC/Microsoft.Office.Interop.Excel/12.0.0.0__71e9bce111e9429c/Microsoft.Office.Interop.Excel.dll'>>> excel.Quit()>>>
In IronPython 2.0 Beta 4, things just work, thanks to the shiny new OleAut support. "wb" is just a simple COM object that supports IDispatch, and there is no need for a PIA anymore!
c:\IronPython-2.0B4>ipy.exe -vIronPython 2.0 Beta (2.0.0.4000) on .NET 2.0.50727.1433Type "help", "copyright", "credits" or "license" for more information.>>> import System>>> t = System.Type.GetTypeFromProgID("Excel.Application")>>> excel = System.Activator.CreateInstance(t)>>> wb = excel.Workbooks.Add()>>> wb<System.__ComObject (_Workbook) object at 0x000000000000002B>>>> excel.Quit()>>>
IDispatch is about invoking a method on an existing OleAut object. But how do you get to an OleAut object in the first place? VBScript and VBA have a method called CreateObject to create an OleAut object. There are multiple ways of doing the same in IronPython.
The first is to use the Type.GetTypeFromProgID method as shown here. This is the simplest solution.
>>> import System>>> t = System.Type.GetTypeFromProgID("Excel.Application")>>> excel = System.Activator.CreateInstance(t)>>> wb = excel.Workbooks.Add()
The second way is to use the new "clr.AddReferenceToTypeLibrary" function. This currently requires hard-coding the GUID of the typelib (we could consider easier ways in the future). However, the advantage is that you can access the typelib just like a Python module. This is particularly handy if you need to access enum values in your program. Using the TLB, you can use the symbolc enum names instead of hard-coding int constants.
>>> import System>>> excelTypeLibGuid = System.Guid("00020813-0000-0000-C000-000000000046")>>> import clr>>> clr.AddReferenceToTypeLibrary(excelTypeLibGuid)>>> from Excel import Application>>> excel = Application() >>> wb = excel.Workbooks.Add()
Finally, you can use the PIA as before. There is no particular advantage to doing this. It just means that your old code will keep on working as before (though method invocation will use the new IDispatch mechanism).
>>> import clr>>> clr.AddReference("Microsoft.Office.Interop.Excel")>>> from Microsoft.Office.Interop.Excel import Application>>> excel = Excel()>>> wb = excel.Workbooks.Add()
All of the above techniques end up calling the Win32 function CoCreateInstance. So one more technique you could use is to make a pinvoke call to CoCreateInstance!
Once you have created a COM object, the rest of the code to access the object should work pretty much like before; it just wont require the presence of a PIA.
If you run into any bugs with the new OleAut support, there is a way to get to the old behavior. This is done by setting an environment variable called COREDLR_PreferComInteropAssembly to "TRUE" as show here. Do report the bug to users@lists.ironpython.com so that the issue can be fixed. COREDLR_PreferComInteropAssembly might go away in the future.
c:\IronPython-2.0B4>ipy.exeIronPython 2.0 Beta (2.0.0.4000) on .NET 2.0.50727.1433Type "help", "copyright", "credits" or "license" for more information.>>> import System>>> t = System.Type.GetTypeFromProgID("Excel.Application")>>> excel = System.Activator.CreateInstance(t)>>> wb = excel.Workbooks.Add()>>> wb<System.__ComObject (_Workbook) object at 0x000000000000002B>>>> ^Z c:\IronPython-2.0B4>set COREDLR_PreferComInteropAssembly=TRUE c:\IronPython-2.0B4>ipy.exeIronPython 2.0 Beta (2.0.0.4000) on .NET 2.0.50727.1433Type "help", "copyright", "credits" or "license" for more information.>>> import System>>> t = System.Type.GetTypeFromProgID("Excel.Application")>>> excel = System.Activator.CreateInstance(t)>>> wb = excel.Workbooks.Add()>>> wb<Microsoft.Office.Interop.Excel.WorkbookClass (Workbook) object at 0x000000000000002B>>>> wb.GetType()<System.RuntimeType object at 0x000000000000002C [Microsoft.Office.Interop.Excel.WorkbookClass]>>>>
There are few expected differences to be aware of.
Currently, there are a few bugs in corner cases:
Running with "ipy.exe -X:ShowRules", you can see that the generated code makes pinvoke calls on the IDispatch interfaces. This is similar to what VBScript (or VBA late bound code) would have done. UnsafeMethods.IDisaptchInvoke is a helper function in Microsoft.Scripting.Core.dll that will dereference the vtable of the OleAut object, get the function pointer for IDispatch::Invoke, and make a call to the function pointer.
>>> wb = com.Workbooks//// AST: Rule// .scope <rule> () { .if ((((.arg $arg0) != .null) && (Marshal.IsComObject)( (.arg $arg0), )) ) {.return .site (Object) GetMember Workbooks( // Python GetMember Workbooks IsNoThrow: False (ComObject.ObjectToComObject)( (.arg $arg0), ) (.arg $arg1) ); } .else {/*empty*/; }} // AST: Rule// .scope <rule> () { .if (((((.arg $arg0) != .null) && (((Object)(.arg $arg0)).(Object.GetType)() == ((Type)IDispatchComObject)) ) && ((IDispatchComObject)(.arg $arg0).ComTypeDesc == (ComTypeDesc)System.Scripting.Com.ComTypeDesc) ) ) {.scope ( System.Scripting.Com.IDispatchObject dispatchObject System.IntPtr dispatchPointer System.Int32 dispId System.Runtime.InteropServices.ComTypes.DISPPARAMS dispParams System.Scripting.Com.Variant invokeResult System.Object returnValue ) { .scope ( ) { { (.var dispId) = (ComMethodDesc)Object& Workbooks().DispId (.var dispParams).cArgs = 0 (.var dispParams).cNamedArgs = 0 (.var dispatchObject) = (IDispatchComObject)(.arg $arg0).DispatchObject (.var dispatchPointer) = ((.var dispatchObject)).(IDispatchObject.GetDispatchPointerInCurrentApartment)() .try { .scope ( System.Scripting.Com.ExcepInfo excepInfo System.UInt32 argErr System.Int32 hresult ) { { (.var hresult) = (UnsafeMethods.IDispatchInvoke)( (.var dispatchPointer), (.var dispId), (INVOKEKIND)INVOKE_FUNC, INVOKE_PROPERTYGET, (.var dispParams), (.var invokeResult), (.var excepInfo), (.var argErr), ) (ComRuntimeHelpers.CheckThrowException)( (.var hresult), (.var excepInfo), (.var argErr), "Workbooks", ) (.var returnValue) = ((.var invokeResult)).(Variant.ToObject)() } } } .finally { { ((.var dispatchObject)).(IDispatchObject.ReleaseDispatchPointer)( (.var dispatchPointer), ) ((.var invokeResult)).(Variant.Clear)() } } .return (.var returnValue); } } } } .else {/*empty*/; }}
.scope <rule> () { .if (((((.arg $arg0) != .null) && (((Object)(.arg $arg0)).(Object.GetType)() == ((Type)IDispatchComObject)) ) && ((IDispatchComObject)(.arg $arg0).ComTypeDesc == (ComTypeDesc)System.Scripting.Com.ComTypeDesc) ) ) {.scope ( System.Scripting.Com.IDispatchObject dispatchObject System.IntPtr dispatchPointer System.Int32 dispId System.Runtime.InteropServices.ComTypes.DISPPARAMS dispParams System.Scripting.Com.Variant invokeResult System.Object returnValue ) { .scope ( ) { { (.var dispId) = (ComMethodDesc)Object& Workbooks().DispId (.var dispParams).cArgs = 0 (.var dispParams).cNamedArgs = 0 (.var dispatchObject) = (IDispatchComObject)(.arg $arg0).DispatchObject (.var dispatchPointer) = ((.var dispatchObject)).(IDispatchObject.GetDispatchPointerInCurrentApartment)() .try { .scope ( System.Scripting.Com.ExcepInfo excepInfo System.UInt32 argErr System.Int32 hresult ) { { (.var hresult) = (UnsafeMethods.IDispatchInvoke)( (.var dispatchPointer), (.var dispId), (INVOKEKIND)INVOKE_FUNC, INVOKE_PROPERTYGET, (.var dispParams), (.var invokeResult), (.var excepInfo), (.var argErr), ) (ComRuntimeHelpers.CheckThrowException)( (.var hresult), (.var excepInfo), (.var argErr), "Workbooks", ) (.var returnValue) = ((.var invokeResult)).(Variant.ToObject)() } } } .finally { { ((.var dispatchObject)).(IDispatchObject.ReleaseDispatchPointer)( (.var dispatchPointer), ) ((.var invokeResult)).(Variant.Clear)() } } .return (.var returnValue); } } } } .else {/*empty*/; }}