When you start Excel through automation, it's kept alive for as long as it is referenced 1. Closing it interactively doesn't necessarily exit the application, in this case because your ole:remote-autotool
for Excel hasn't been released.
One way to let Excel exit is to get rid of the autotool wrappers, e.g. if you have one stored in a variable or in an object's slot, set its value to nil
:
(let ((excel-app (ole:ask-for-autotool "Excel.Application"
ole:CLSCTX_LOCAL_SERVER)))
;; ...
;; Break the reference to all autotool wrappers you no longer need
(setf excel-app nil))
In Allegro CL's OLE Interface, the creation of an IUnknown
client wrapper registers a finalization that releases the underlying interface pointer when it's garbage collected.
An autotool is yet another wrapper, which containts an IDispatch
client wrapper (it inherits from IUnknown
, as all COM interfaces), easing its use in the same way as scripting languages with what is known as late binding in COM automation. When you stop referring to the autotool, the underlying IDispatch
/IUnknown
client wrapper may be garbage collected, and thus released.
However, Excel might not quit promptly this way, unless you're expecting or forcing a garbage collection to happen 2. If you want Excel to exit immediately without interaction, you should Quit
Excel with DisplayAlerts
set to false and then ole:release
your autotool objects (note: I haven't tested this thoroughly):
(let ((excel-app (ole:ask-for-autotool "Excel.Application"
ole:CLSCTX_LOCAL_SERVER)))
;; ...
;; Disable prompts asking to save unsaved workbooks
;; They will not be saved
(setf (ole:auto-getf excel-app "DisplayAlerts") nil)
(ole:auto-method excel-app "Quit")
;; Manually release all autotool wrappers you no longer need
(ole:release excel-app))
The scheduled finalization for the autotool won't fail after we release it manually. It checks if the underlying interface pointer wrapper has been released, probably by checking if its class is now ole::released-IUnknown
3, as it is documented to happen in the description of ole:release (ole:IUnknown-Client)
.
There's also a brute-force way using ole:release-process-client-interfaces
, but you should make sure you won't access any OLE objects created so far in the current lisp process (thread):
(mp:process-run-function
"Excel worker"
#'(lambda ()
(ole:start-ole)
(unwind-protect
(let ((excel-app (ole:ask-for-autotool "Excel.Application"
ole:CLSCTX_LOCAL_SERVER)))
;; ...
;; Disable prompts asking to save unsaved workbooks
;; They will not be saved
(setf (ole:auto-getf excel-app "DisplayAlerts") nil)
(ole:auto-method excel-app "Quit")
;; Make sure this is the last thing you do, the wrappers
;; created in the current process will become invalid
(ole:release-process-client-interfaces))
;; Given this call, the brute force doesn't make much sense anyway
(ole:stop-ole))))
1. As long as a lock to any registered factory is held or a reference to any object created by excel.exe is kept, it should keep running. This is normal for out-of-process servers.
2. If your autotool instance has been tenured, its generation needs to be garbage collected, often achievable with a global GC, to be finalized and thus released.
3. In Common Lisp, you can change the class of an object.