Outlook Addin - Runtime Exceptions

Add-in Express™ Support Service
That's what is more important than anything else

Outlook Addin - Runtime Exceptions
When debugging Runtime Exceptions are being thrown in the Immediate Window 
David Wisniewski




Posts: 39
Joined: 2015-11-20
I'm not sure how to track down what is causing these exceptions to appear in the immediate window when I start debugging because it's not stopping outlook from starting or the addin from loading and not halting code exception. Can you please assist in how to find what is causing these execeptions so that I don't release a product that is causing errors behind the scenes?

This is the output I get in the Immediate Window, and I don't know how to find where they are being thrown:


Exception thrown: 'System.Runtime.InteropServices.COMException' in OutlookMacrosV2.dll
Exception thrown: 'System.ArgumentException' in OutlookMacrosV2.dll
Exception thrown: 'System.ArgumentException' in OutlookMacrosV2.dll
Exception thrown: 'System.ArgumentException' in OutlookMacrosV2.dll
Exception thrown: 'System.ArgumentException' in OutlookMacrosV2.dll
Exception thrown: 'System.ArgumentException' in OutlookMacrosV2.dll
Exception thrown: 'System.ArgumentException' in OutlookMacrosV2.dll
Exception thrown: 'System.ArgumentException' in OutlookMacrosV2.dll

Posted 08 Dec, 2021 20:23:37 Top
David Wisniewski




Posts: 39
Joined: 2015-11-20
I figured it out, in VS if you press Ctrl+Alt+E it brings up the exception settings. I searched for the exception type and told it to break on those exceptions and then it took me straight to them the next time I ran in debug mode. All errors fixed and this is the first time in 8 years that I've had my addin error free.

One last question... how can I find out if I have COM objects that I'm not releasing? Because my addin is being used by a client that almost never ever turns off his laptop or closes excel, I'm afraid of accummulating too many resources by unreleased com objects. I've done my best to go through the code, but it's very extensive, and there are some parts where I'm honestly not 100% sure that I'm handling the comobjects properly.
Posted 09 Dec, 2021 01:32:41 Top
Andrei Smolin


Add-in Express team


Posts: 18183
Joined: 2006-05-11
Hello David,

David Wisniewski writes:
how can I find out if I have COM objects that I'm not releasing?


There's no way. You can try to start GC.Collect() on rare occasions but looking for an issue in such a scenario may be close to impossible.

Call GC.Collect(), GC.WaitForPendingFinalizers(), then again GC.Collect() and again GC.WaitForPendingFinalizers().

We wrote about using GC.Collect() in Releasing COM objects: Garbage Collector vs. Marshal.RelseaseComObject.

Regards from Poland (CET),

Andrei Smolin
Add-in Express Team Leader
Posted 09 Dec, 2021 02:51:58 Top
Craig Callender




Posts: 8
Joined: 2021-07-06
FYI, according to MSDN (under Remarks), there is no guarantee that GC.WaitForPendingFinalizers() will ever return and may block forever. I've actually seen this consistently happen in a customer environment. The MSDN suggestion is it call it from another thread and time it out. We wrote the following class to do this for us:


public class GarbageCollectorDaemon
{
    private static readonly SemaphoreSlim CollectionSemaphore = new SemaphoreSlim(1, 1);
    private static readonly Lazy<Timer> LazyTimer = new Lazy<Timer>(CreateTimer, LazyThreadSafetyMode.ExecutionAndPublication);

    // The interval to force garbage collection.
    private static readonly TimeSpan Interval = TimeSpan.FromMinutes(45);

    // How long to wait before seeing if the garbage collection thread is finished.
    private static readonly TimeSpan DelayInterval = TimeSpan.FromMilliseconds(100);

    // The maximum amount of time to wait for the garbage collection thread to finish.
    private static readonly TimeSpan MaxWaitTime = TimeSpan.FromSeconds(3);

    public static Timer Timer => LazyTimer.Value;

    public static Timer Initialize()
    {
        return LazyTimer.Value;
    }

    /// <summary>
    /// Safely calls Garbage collection by putting it on a separate thread and aborting that thread if it runs for too long.
    /// </summary>
    /// <returns>A boolean indicating if GC.Collect() was called.</returns>
    public static async Task<bool> SafelyCollectAsync()
    {
        var collectionPerformed = false;

        // Don't queue up requests, just enter if it's not being executed; otherwise skip.
        if (await CollectionSemaphore.WaitAsync(0))
        {
            try
            {
                var threadTimer = new Stopwatch();
                var gcThread = new Thread(UnsafeRelease);

                try
                {
                    gcThread.Start();
                    threadTimer.Start();

                    // GC.Collect() was called, so return true.
                    collectionPerformed = true;

                    while (gcThread.IsAlive && threadTimer.Elapsed < MaxWaitTime)
                    {
                        await Task.Delay(DelayInterval);
                    }
                }
                finally
                {
                    threadTimer.Stop();

                    // If we're still alive when we get here, kill the thread.
                    if (gcThread.IsAlive)
                    {
                        gcThread.Abort();
                    }
                }
            }
            finally
            {
                CollectionSemaphore.Release();
            }
        }
        else
        {
            // Logger.Warn("Did not initialize garbage collection as it was already executing.");
        }

        return collectionPerformed;
    }

    private static Timer CreateTimer()
    {
        var timer = new Timer
        {
            Interval = Interval.TotalMilliseconds,
            AutoReset = true,
            Enabled = true
        };
        timer.Elapsed += OnTimedEventAsync;

        return timer;
    }

    private static async void OnTimedEventAsync(object source, ElapsedEventArgs e)
    {
        await SafelyCollectAsync();
    }

    /// <summary>
    /// The below code is not guaranteed to return and may block indefinitely.  Ensure you call from a thread you can abort.
    /// </summary>
    private static void UnsafeRelease()
    {
        try
        {
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }
        catch (ThreadAbortException exception)
        {
            // Logger.Error(exception, "Garbage collection was aborted by the controlling thread.");
        }
        catch(Exception exception)
        {
            // Logger.Error(exception, "There was an exception thrown during garbage collection.");
        }
    }
}
Posted 16 Dec, 2021 11:26:13 Top
Andrei Smolin


Add-in Express team


Posts: 18183
Joined: 2006-05-11
Hello Craig,

Thank you very much!

Regards from Poland (CET),

Andrei Smolin
Add-in Express Team Leader
Posted 17 Dec, 2021 13:07:22 Top