Skip to content

Commit

Permalink
Change on-demand COM registration to prevent RTD Server memory leaks
Browse files Browse the repository at this point in the history
  • Loading branch information
govert committed Apr 11, 2015
1 parent d2cdd00 commit a32261d
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 92 deletions.
70 changes: 8 additions & 62 deletions Source/ExcelDna.Integration/ComRegistration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -364,78 +364,24 @@ protected override void Deregister()
}
}

internal class ClassFactoryRegistration : Registration
{
private const DWORD CLSCTX_INPROC_SERVER = 0x1;
private const DWORD REGCLS_SINGLEUSE = 0;
private const DWORD REGCLS_MULTIPLEUSE = 1;

private DWORD _classRegister;

public ClassFactoryRegistration(Type type, CLSID clsId)
{
ClassFactory factory = new ClassFactory(type);
IntPtr pFactory = Marshal.GetIUnknownForObject(factory);
HRESULT result = ComAPI.CoRegisterClassObject(ref clsId, pFactory,
CLSCTX_INPROC_SERVER, REGCLS_MULTIPLEUSE, out _classRegister);

if (result != ComAPI.S_OK)
{
throw new InvalidOperationException("CoRegisterClassObject failed.");
}
}

protected override void Deregister()
{
if (_classRegister != 0)
{
HRESULT result = ComAPI.CoRevokeClassObject(_classRegister);
if (result != ComAPI.S_OK)
{
Debug.Print("ClassFactory deregistration failed. Result: {0}", result);
}
_classRegister = 0;
}
}
}

// Implements the IClassFactory factory registration by (temporarily) adding it to the list of classes implemented through the ComServer.
// We then write the registry keys that will point Excel to the .xll as the provider of this class,
// and the ComServer will handle the DllGetClassObject call, returning the IClassFactory.
internal class SingletonClassFactoryRegistration : Registration
{
private const DWORD CLSCTX_INPROC_SERVER = 0x1;
private const DWORD REGCLS_SINGLEUSE = 0;
private const DWORD REGCLS_MULTIPLEUSE = 1;

private object _instance;
private DWORD _classRegister;

CLSID _clsId;
public SingletonClassFactoryRegistration(object instance, CLSID clsId)
{
_instance = instance;
_clsId = clsId;
SingletonClassFactory factory = new SingletonClassFactory(instance);
IntPtr pFactory = Marshal.GetIUnknownForObject(factory);
// In versions < 0.29 we registered as REGCLS_SINGLEUSE even though it is not supposed to work for inproc servers.
// It seems to do no harm to keep the ClassObject around.
HRESULT result = ComAPI.CoRegisterClassObject(ref clsId, pFactory,
CLSCTX_INPROC_SERVER, REGCLS_MULTIPLEUSE, out _classRegister);

if (result != ComAPI.S_OK)
{
throw new InvalidOperationException("CoRegisterClassObject failed.");
}
ComServer.RegisterClassFactory(_clsId, factory);
}

protected override void Deregister()
{
if (_classRegister != 0)
{
HRESULT result = ComAPI.CoRevokeClassObject(_classRegister);
if (result != ComAPI.S_OK)
{
Debug.Print("ClassFactory deregistration failed. Result: {0}", result);
}
_classRegister = 0;
}
ComServer.UnregisterClassFactory(_clsId);
}
}

#endregion
}
75 changes: 52 additions & 23 deletions Source/ExcelDna.Integration/ComServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
using System.Diagnostics;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Security;
using Microsoft.Win32;
using ExcelDna.Integration;
Expand All @@ -37,17 +38,19 @@ namespace ExcelDna.ComInterop
using HRESULT = System.Int32;
using IID = System.Guid;
using CLSID = System.Guid;
using System.Runtime.InteropServices.ComTypes;

// The Excel-DNA .xll can also act as an in-process COM server.
// This is implemented to support direct use of the RTD servers from the worksheet
// using the =RTD(...) function.
// TODO: Add explicit registration of types?
// TODO: Add on-demand registration.
public static class ComServer
{
// Internal COM Server support.
// COM Server support for persistently registered types.
static readonly List<ExcelComClassType> registeredComClassTypes = new List<ExcelComClassType>();

// Used for on-demand COM add-in (and RTD Server and CTP control) registration.
// Added and removed from the Dictionary as we go along.
static readonly Dictionary<CLSID, ComAPI.IClassFactory> registeredClassFactories = new Dictionary<CLSID, ComAPI.IClassFactory>();

internal static void RegisterComClassTypes(List<ExcelComClassType> comClassTypes)
{
Expand All @@ -61,8 +64,19 @@ internal static void RegisterComClassType(ExcelComClassType comClassType)
registeredComClassTypes.Add(comClassType);
}

// This may also be called by an add-in wanting to register
// CONSIDER: Should this rather use RegistrationServices class?
internal static void RegisterClassFactory(CLSID clsId, ComAPI.IClassFactory classFactory)
{
// CONSIDER: Do we need to deal with the case where it is already in the list?
// For now we expect it to throw...
registeredClassFactories.Add(clsId, classFactory);
}

internal static void UnregisterClassFactory(CLSID clsId)
{
registeredClassFactories.Remove(clsId);
}

// This may also be called by an add-in wanting to register programmatically (e.g. in AutoOpen)
public static HRESULT DllRegisterServer()
{
foreach (ExcelComClassType comClass in registeredComClassTypes)
Expand All @@ -73,7 +87,7 @@ public static HRESULT DllRegisterServer()
return ComAPI.S_OK;
}

// This may also be called by an add-in wanting to unregister
// This may also be called by an add-in wanting to unregister programmatically
public static HRESULT DllUnregisterServer()
{
foreach (ExcelComClassType comClass in registeredComClassTypes)
Expand All @@ -90,34 +104,49 @@ internal static HRESULT DllGetClassObject(CLSID clsid, IID iid, out IntPtr ppunk
ppunk = IntPtr.Zero;
return ComAPI.E_INVALIDARG;
}
foreach (ExcelComClassType comClass in registeredComClassTypes)

ComAPI.IClassFactory factory;
if (registeredClassFactories.TryGetValue(clsid, out factory) ||
TryGetComClassType(clsid, out factory))
{
if (comClass.ClsId == clsid)
{
ClassFactory factory = new ClassFactory(comClass);
IntPtr punkFactory = Marshal.GetIUnknownForObject(factory);
HRESULT hrQI = Marshal.QueryInterface(punkFactory, ref iid, out ppunk);
Marshal.Release(punkFactory);
if (hrQI == ComAPI.S_OK)
{
return ComAPI.S_OK;
}
else
{
return ComAPI.E_UNEXPECTED;
}
}
IntPtr punkFactory = Marshal.GetIUnknownForObject(factory);
HRESULT hrQI = Marshal.QueryInterface(punkFactory, ref iid, out ppunk);
Marshal.Release(punkFactory);
if (hrQI == ComAPI.S_OK)
{
return ComAPI.S_OK;
}
else
{
return ComAPI.E_UNEXPECTED;
}
}

// Otherwise it was not found
ppunk = IntPtr.Zero;
return ComAPI.CLASS_E_CLASSNOTAVAILABLE;
}

static bool TryGetComClassType(CLSID clsId, out ComAPI.IClassFactory factory)
{
// Check among the persistently registered classes
foreach (ExcelComClassType comClass in registeredComClassTypes)
{
if (comClass.ClsId == clsId)
{
factory = new ClassFactory(comClass);
return true;
}
}
factory = null;
return false;
}

internal static HRESULT DllCanUnloadNow()
{
// CONSIDER: Allow unloading - but how to keep track of this.....?
return ComAPI.S_FALSE;
}

}

internal class ExcelComClassType
Expand Down
10 changes: 5 additions & 5 deletions Source/ExcelDna.Integration/ExcelRtd2010BugHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ namespace ExcelDna.Integration
// when the calling formula has inputs depending on another cell.
// E.g. A1: 5, B1: =MyRtdFunc(A1) where MyRtdFunc calls some RTD server with the input as a parameter.
// When A1 is changed, MyRtfFunc is called, the new RTD topic created, but DisconnectData never called.
// The bug is discussed in this thrad: http://social.msdn.microsoft.com/Forums/en-US/exceldev/thread/ba06ac78-7b64-449b-bce4-9a03ac91f0eb/
// The bug is discussed in this therad: http://social.msdn.microsoft.com/Forums/en-US/exceldev/thread/ba06ac78-7b64-449b-bce4-9a03ac91f0eb/

// TODO: If a formula is moved from one cell to another, or some rows / cols are deleted, the UDFs will not be called automatically again.
// Consider whether this might be a problem for us here.
Expand All @@ -58,11 +58,11 @@ static ExcelRtd2010BugHelper()
ExcelVersionHasRtdBug = true;
return;
}
#if DEBUG
ExcelVersionHasRtdBug = true; // To test a bit...
#else
//#if DEBUG
// ExcelVersionHasRtdBug = true; // To test a bit...
//#else
ExcelVersionHasRtdBug = false;
#endif
//#endif
}

static Dictionary<string, ExcelRtd2010BugHelper> _wrappers;
Expand Down
2 changes: 0 additions & 2 deletions Source/ExcelDna.Integration/ExcelRtdServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,4 @@ void IRtdServer.ServerTerminate()
}
}
}


}

0 comments on commit a32261d

Please sign in to comment.