Search code examples
vsixvs-extensibilityvsxdsl-tools

How to load a ModelingPackage (DSL-Tools) asynchronously in Visual Studio 2019?


I have a DSL-tools project where I use [ProvideAutoLoad] because it adds a set of menu commands to Visual Studio to allow the user to transform code (we have lots of text templates), and few other things.

Since VS2019 does not allow to autoload sync packages anymore, I get that annoying warnings even when the option to allow autoload is on.

Apparently Microsoft has no plans to provide an async version of ModelingPackage (I am in fact begining to question if their not in the way to descontinue DSL-tools alltogether).

As anyone found a way of working around this?

I have tried to use another package - built as AsyncPackage - so that one would load my DSL-tools package on InitializeAsync() but I end up with all sorts of exceptions using the IVsShell service.

protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<VSShell.ServiceProgressData> progress)
{
    // Default behavior

    await base.InitializeAsync(cancellationToken, progress).ConfigureAwait(false);

    // Load the service designer package

    this.EnsureDesignerPackage();

    // Switche to the UI thread

    await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

    // Menu commands

    this.InitializeMenuCommands();
}

private void EnsureDesignerPackage()
{
    // Package already loaded?

    if (!this.IsDesignerPackageLoaded())
    {
        this.LoadDesignerPackage();
    }
}

private bool IsDesignerPackageLoaded()
{
    // Package identifier

    Guid packageId = new Guid(GlobalConstants.ServiceDesignerPackageId);

    // Is loaded?

    IVsShell service = this.VsShellService;

    int hr = this.VsShellService.IsPackageLoaded(ref packageId, out IVsPackage package);
    if (ErrorHandler.Succeeded(hr) && package != null)
    {
        return true;
    }

    // Default result

    return false;
}

private void LoadDesignerPackage()
{
    // Package identifier

    Guid packageId = new Guid(GlobalConstants.ServiceDesignerPackageId);

    // Not loaded?

    int hr = this.VsShellService.IsPackageLoaded(ref packageId, out IVsPackage package);
    if (hr != VSConstants.S_OK || package == null)
    {
        // Load

        hr = this.VsShellService.LoadPackage(ref packageId, out package);
        if (ErrorHandler.Failed(hr))
        {
            string message = "Service designer loading failed: {0}.".Format().With(hr);
            this.ShowException(message);
        }
    }
}

Solution

  • I just happened to walk a customer through this same scenario earlier today. Unfortunately, the last time I asked there were no current plans to update the underlying DSL framework to leverage AsyncPackage or similar. I saw you already logged a suggestion in the dev community site, and just up voted and added my own $.02. I would encourage anyone hitting this to up vote this suggestion linked above.

    To add support for Async Load, to your DSL Package object, you'll want to add a reference the Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime.dll assembly, and then edit your Package.tt file, to add a VSShell.PackageRegistration attribute with AllowsBackgroundLoading set to true, and implement the IAsyncLoadablePackageInitialize interface on that same class. For example:

    namespace <#= CodeGenerationUtilities.GetPackageNamespace(this.Dsl) #>
    {
        /// <summary>
        /// Double-derived class to allow easier code customization.
        /// </summary>
        [VSShell::ProvideMenuResource("1000.ctmenu", 1)]
        [VSShell::ProvideToolboxItems(1)]
        [VSShell::PackageRegistration(AllowsBackgroundLoading = true)]
        [global::Microsoft.VisualStudio.TextTemplating.VSHost.ProvideDirectiveProcessor(typeof(global::<#= this.Dsl.Namespace #>.<#= directiveName #>DirectiveProcessor), global::<#= this.Dsl.Namespace #>.<#= directiveName #>DirectiveProcessor.<#= directiveName #>DirectiveProcessorName, "A directive processor that provides access to <#= directiveName #> files")]
        [global::System.Runtime.InteropServices.Guid(Constants.<#= dslName #>PackageId)]
        internal sealed partial class <#= dslName #>Package : <#= dslName #>PackageBase, VSShellInterop.IAsyncLoadablePackageInitialize
        {
            public VSShellInterop.IVsTask Initialize(VSShellInterop.IAsyncServiceProvider pServiceProvider, VSShellInterop.IProfferAsyncService pProfferService, VSShellInterop.IAsyncProgressCallback pProgressCallback)
            {
                // do async initialization here
                return null;
            }
        }
    }
    

    Sincerely,