2.0: Stabilizing

The latest release packs a number of fundamental changes that affected every single part of Rubberduck. These changes are the result of the payment of a massive technical debt: from the very beginning, the very idea of wrapping the VBIDE API was deemed a massive and not-quite-needed undertaking – and so many parts of Rubberduck had direct dependencies on the VBA extensibility type library.

VBProjects, VBComponents, CodePanes, CommandBars, everything.

The problem is that, with COM resources, you need to release what you use – and cleaning up COM resources through .NET interop isn’t exactly trivial. Of course it looks pretty simple if you look at the ShutdownAddIn method in Rubberduck’s core:

try
{
    _ide.Release();
}
catch (Exception e)
{
    _logger.Error(e);
}

This Release method is exposed by the ISafeComWrapper interface, which is itself derived from a very general-purpose INullObjectWrapper interface:

public interface ISafeComWrapper : INullObjectWrapper
{
    void Release();
}

public interface ISafeComWrapper<out T> : ISafeComWrapper
{
    new T Target { get; }
}

public interface INullObjectWrapper
{
    object Target { get; }
    bool IsWrappingNullReference { get; }
}

Together, these simple interfaces form the down payment on the technical debt that has been plaguing Rubberduck from its early beginnings: the next step was to derive wrapper interfaces for every single type in the API. So instead of depending on a Microsoft.Vbe.Interop.CodePane, our code would be depending on a Rubberduck.VBEditor.SafeComWrappers.Abstract.ICodePane interface:

public interface ICodePane : ISafeComWrapper, IEquatable<ICodePane>
{
    IVBE VBE { get; }
    ICodePanes Collection { get; }
    IWindow Window { get; }
    int TopLine { get; set; }
    int CountOfVisibleLines { get; }
    ICodeModule CodeModule { get; }
    CodePaneView CodePaneView { get; }
    Selection GetSelection();
    QualifiedSelection? GetQualifiedSelection();
    void SetSelection(int startLine, int startColumn, int endLine, int endColumn);
    void SetSelection(Selection selection);
    void Show();
}

These abstract wrapper interfaces can then be implemented by concrete wrapper types that directly deal with the Microsoft interop types – so we could get rid of a number of extension methods, and move them directly into the wrapper types… and this time we’re not limited to the VBA extensibility API:

icodepaneimplementations

Every wrapper type must implement the Release method, where it cleans up after itself and its child objects – here the CodePanes wrapper releases its CodePane children before it releases itself:

public override void Release()
{
    if (!IsWrappingNullReference)
    {
        for (var i = 1; i <= Count; i++)
        {
            this[i].Release();
        }
        Marshal.ReleaseComObject(Target);
    }
}

This way, the entry point can call IVBE.Release, and every COM object ever accessed in the lifetime of Rubberduck is sure to get properly cleaned up, with one single method call.


Everywhere in Rubberduck, dependencies on Microsoft.Vbe.Interop were replaced with dependencies on Rubberduck.VBEditor.SafeComWrappers.Abstract, and then we were a step closer to a stable release.

But Rubberduck still kept crashing its host application on exit, and sometimes even at startup: something else wasn’t right, the call stack was pointing to an event listener picking up ItemAdded and ItemRemoved events fired from the References class.

Rubberduck implements COM event sinks to listen to IDE events such as “a module was renamed” or “a new project was added”, or “a project was closed”; I disabled them all. I also disabled the initial parse, just in case.

 

And Rubberduck kept crashing the host on exit, still.

The wrapper type that implemented the IReferences wrapper interface was registering handlers for ItemAdded and ItemRemoved; I removed them.

And Rubberduck did not crash on exit anymore.

So I reinstated the keyboard hook: still no crash.

So I reinstated the event sinks: still no crash.

So I reinstated the initial parse… and it crashed.

Initializing a VBE add-in is… complicated: the VBE hasn’t completely constructed itself at that time, and projects are still loading – the event sink was picking up ProjectAdded and ProjectRenamed events at startup (did you know your VBA projects aren’t “natively” called “VBAProject1”, but literally renamed that way at startup?), and trying to parse projects in the IDE at that point was just asking for trouble.

So I disabled the initial parse. No crash at startup, no crash at shut down. I moved the sinks’ event registration code out of constructors and into a dedicated method meant to be called once the IDE is ready for it… and forgot to call it.

So v2.0.9 does not parse automatically at startup, and doesn’t automatically know when a module or project is renamed, added, or removed.

But it doesn’t crash either. At least, not on my machine running 64-bit Office 2010 on Win10 x64… but since 2.0.9 was issued earlier today, we’ve found a race condition preventing proper teardown of the Rubberduck menu, and there are still a few more things to fix before we can call 2.0 “stable”.

But we’re much closer today than we were a month ago.

To be continued…

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s