I was unable to resolve the zombie problem using the recommended VBA only method, even after careful checking for and removal of circular references.
Additional searching turned up a way to call the ReleaseCom
method using code that wraps System.Runtime.InteropServices.Marshal.FinalReleaseComObject
to create a COM visible dll that can be called from VBA
Using the tutorial "how to create a com object using vs 2008 and consume it from vb6.0 client" and a newly installed copy of VS2010 Express, I was able to create a COM visible dll callable from VBA.
Here's the slightly modified wrapper and how to build the dll:
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.EnterpriseServices;
namespace ComDisposerLib
{
[ClassInterface(ClassInterfaceType.None)]
[ComponentAccessControl(false)]
public class ComDisposer : System.EnterpriseServices.ServicedComponent, IDisposable, ComDisposerLib.IComDispose
{
private List<Object> _comObjs;
public ComDisposer()
{
_comObjs = new List<Object>();
}
~ComDisposer()
{
Dispose(false);
}
public Object Add(Object o)
{
if (o != null && o.GetType().IsCOMObject)
_comObjs.Add(o);
return o;
}
public void Clear()
{
Dispose(true);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
for (int i = _comObjs.Count - 1; i >= 0; --i)
Marshal.FinalReleaseComObject(_comObjs[i]);
_comObjs.Clear();
}
}
void IDisposable.Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
and the interface:
using System;
namespace ComDisposerLib
{
public interface IComDispose
{
Object Add(Object o);
void Clear();
void Dispose();
}
}
To build, create a new class library project, add references to System.Runtime.InteropServices
and System.EnterpriseServices
, enable 'Sign the assembly' (in the menu under project / properties / signing ) and select or create a key file. Add the class and interface code files. In the AssemblyInfo.cs file (located under properties) add
using System.Runtime.InteropServices;
using System.EnterpriseServices;
and
[assembly: ComVisible(true)]
[assembly: ApplicationName("ComDisposer")]
[assembly: ApplicationActivation(ActivationOption.Library)]
and build. If all goes well, you can register you dll as follows:
regsvcs "C:\Documents and Settings\username\My Documents\Visual Studio 2010\Projects\ComDispose\ComDispose\obj\Release\ComDisposer.dll"
In VBA, after adding a reference to your new COM library, use it as follows:
Sub disposeGlobalComObjects()
' global scope objects used only to simplify example
Dim cd As ComDisposer
Set cd = New ComDisposer
If Not SomeGlobalComObject Is Nothing Then
cd.Add SomeGlobalComObject
Set SomeGlobalComObject = Nothing
End If
If Not AnotherGlobalComObject Is Nothing Then
cd.Add AnotherGlobalComObject
Set AnotherGlobalComObject = Nothing
End If
cd.Dispose
End Sub
Early testing indicates that it's working, i.e. Excel closes cleanly and no longer creates zombie processes.
Interestingly, I just ran across this method for using your dll from VBA without having to register it first which could be useful if you didn't have access to the registry on your client machine.