Partilhar via


Escolha o modelo de extensibilidade do Visual Studio certo para você

Você pode estender o Visual Studio usando três modelos principais de extensibilidade, VSSDK, Community Toolkit e VisualStudio.Extensibility. Este artigo aborda os prós e contras de cada um. Usamos um exemplo simples para destacar as diferenças arquitetônicas e de código entre os modelos.

VSSDK

VSSDK (ou SDK do Visual Studio) é o modelo no qual a maioria das extensões no Visual Studio Marketplace se baseiam. Esse modelo é no qual o próprio Visual Studio é construído. É o mais completo e o mais poderoso, mas também o mais complexo de aprender e usar corretamente. As extensões que usam VSSDK são executadas no mesmo processo que o próprio Visual Studio. Carregar no mesmo processo que o Visual Studio significa que uma extensão que tem uma violação de acesso, loop infinito ou outros problemas pode falhar ou travar o Visual Studio e degradar a experiência do cliente. E como as extensões são executadas no mesmo processo que o Visual Studio, elas só podem ser criadas usando .NET Framework. Os extensores que desejam usar ou incorporar bibliotecas que usam .NET 5 e posterior não podem fazer isso usando o VSSDK.

As APIs no VSSDK foram agregadas ao longo dos anos à medida que o próprio Visual Studio se transformava e evoluía. Em uma única extensão, você pode se ver lidando com APIs baseadas em COM deda impressão legada, passando pela simplicidade enganosa de DTE e mexendo com MEF importações e exportações. Vamos dar um exemplo de escrever uma extensão que lê o texto do sistema de arquivos e o insere no início do documento ativo atual dentro do editor. O trecho a seguir mostra o código que você escreveria para manipular quando um comando é invocado em uma extensão baseada em VSSDK:

private void Execute(object sender, EventArgs e)
{
    var textManager = package.GetService<SVsTextManager, IVsTextManager>();
    textManager.GetActiveView(1, null, out IVsTextView activeTextView);

    if (activeTextView != null && activeTextView is IVsTextViewEx nativeView)
    {
        ErrorHandler.ThrowOnFailure(nativeView.GetWindowFrame(out object frameValue));

        IComponentModel2 compService = package.GetService<SComponentModel, IComponentModel2>();
        IVsEditorAdaptersFactoryService editorAdapter = compService.GetService<IVsEditorAdaptersFactoryService>();
        var wpfTextView = editorAdapter?.GetWpfTextView(activeTextView);

        if (frameValue is IVsWindowFrame frame && wpfTextView != null)
        {
            var fileText = File.ReadAllText(Path.Combine(Path.GetTempPath(), "test.txt"));
            wpfTextView.TextBuffer?.Insert(0, fileText);
        }
    }
}

Além disso, você também precisaria fornecer um arquivo .vsct, que define a configuração do comando, como onde colocá-lo na interface do usuário, o texto associado e assim por diante:

<Commands package="guidVSSDKPackage">
    <Groups>
        <Group guid="guidVSSDKPackageCmdSet" id="MyMenuGroup" priority="0x0600">
        <Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_TOOLS" />
        </Group>
    </Groups>

    <Buttons>
        <Button guid="guidVSSDKPackageCmdSet" id="InsertTextCommandId" priority="0x0100" type="Button">
        <Parent guid="guidVSSDKPackageCmdSet" id="MyMenuGroup" />
        <Icon guid="guidImages" id="bmpPic1" />
        <Strings>
            <ButtonText>Invoke InsertTextCommand (Unwrapped Community Toolkit)</ButtonText>
        </Strings>
        </Button>
        <Button guid="guidVSSDKPackageCmdSet" id="cmdidVssdkInsertTextCommand" priority="0x0100" type="Button">
        <Parent guid="guidVSSDKPackageCmdSet" id="MyMenuGroup" />
        <Icon guid="guidImages1" id="bmpPic1" />
        <Strings>
            <ButtonText>Invoke InsertTextCommand (VSSDK)</ButtonText>
        </Strings>
        </Button>
    </Buttons>

    <Bitmaps>
        <Bitmap guid="guidImages" href="Resources\InsertTextCommand.png" usedList="bmpPic1, bmpPic2, bmpPicSearch, bmpPicX, bmpPicArrows, bmpPicStrikethrough" />
        <Bitmap guid="guidImages1" href="Resources\VssdkInsertTextCommand.png" usedList="bmpPic1, bmpPic2, bmpPicSearch, bmpPicX, bmpPicArrows, bmpPicStrikethrough" />
    </Bitmaps>
</Commands>

Como você pode ver no exemplo, o código pode parecer pouco intuitivo e é improvável que alguém familiarizado com o .NET pegue facilmente. Há muitos conceitos para aprender e os padrões de API para acessar o texto do editor ativo são antiquados. Para a maioria dos extensores, as extensões VSSDK são construídas a partir da cópia e colagem de fontes online, o que pode levar a sessões de depuração difíceis, tentativa e erro e frustração. Em muitos casos, as extensões VSSDK podem não ser a maneira mais fácil de atingir os objetivos de extensão (embora, às vezes, eles sejam a única escolha).

Kit de ferramentas da comunidade

Community Toolkit é o modelo de extensibilidade baseado na comunidade de código aberto para Visual Studio que encapsula o VSSDK para uma experiência de desenvolvimento mais fácil. Como ele é baseado no VSSDK, ele está sujeito às mesmas limitações que o VSSDK (ou seja, somente .NET Framework, nenhum isolamento do resto do Visual Studio e assim por diante). Continuando com o mesmo exemplo de escrever uma extensão que insere o texto lido do sistema de arquivos, usando o Community Toolkit, a extensão seria escrita da seguinte forma para um manipulador de comandos:

protected override async Task ExecuteAsync(OleMenuCmdEventArgs e)
{
    DocumentView docView = await VS.Documents.GetActiveDocumentViewAsync();
    if (docView?.TextView == null) return;
    var fileText = File.ReadAllText(Path.Combine(Path.GetTempPath(), "test.txt"));
    docView.TextBuffer?.Insert(0, fileText);
}

O código resultante é muito melhorado do VSSDK em termos de simplicidade e intuição! Não só diminuímos o número de linhas significativamente, mas o código resultante também parece razoável. Não há necessidade de entender qual é a diferença entre SVsTextManager e IVsTextManager. As APIs são mais alinhadas com o .NET, adotando padrões comuns de nomenclatura e assíncronos, juntamente com a priorização de operações comuns. No entanto, o Community Toolkit ainda é construído sobre o modelo VSSDK existente e, portanto, vestígios da estrutura subjacente são perceptíveis. Por exemplo, um arquivo .vsct ainda é necessário. Embora o Community Toolkit faça um ótimo trabalho simplificando as APIs, ele está vinculado às limitações do VSSDK e não tem uma maneira de simplificar a configuração da extensão.

VisualStudio.Extensibilidade

VisualStudio.Extensibility é o novo modelo de extensibilidade em que as extensões são executadas fora do processo principal do Visual Studio. Devido a essa mudança fundamental na arquitetura, novos padrões e recursos estão agora disponíveis para extensões que não são possíveis com o VSSDK ou o Community Toolkit. O VisualStudio.Extensibility oferece um conjunto completamente novo de APIs que são consistentes e fáceis de usar, permite que as extensões direcionem o .NET, isola bugs que surgem de extensões do resto do Visual Studio e permite que os usuários instalem extensões sem reiniciar o Visual Studio. No entanto, como o novo modelo é construído em uma nova arquitetura subjacente, ele ainda não tem a amplitude que o VSSDK e o Community Toolkit têm. Para preencher essa lacuna, você pode executar suas extensões VisualStudio.Extensibility em processo, o que permite que você continue usando APIs VSSDK. No entanto, isso significa que sua extensão só pode ter como destino o .NET Framework, pois ele compartilha o mesmo processo que o Visual Studio, que é baseado no .NET Framework.

Continuando com o mesmo exemplo de escrever uma extensão que insere o texto de um arquivo, usando VisualStudio.Extensibility, a extensão seria escrita da seguinte forma para manipulação de comandos:

public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken)
{
    var activeTextView = await context.GetActiveTextViewAsync(cancellationToken);
    if (activeTextView is not null)
    {
        var editResult = await Extensibility.Editor().EditAsync(batch =>
        {
            var fileText = File.ReadAllText(Path.Combine(Path.GetTempPath(), "test.txt"));

            ITextDocumentEditor editor = activeTextView.Document.AsEditable(batch);
            editor.Insert(0, fileText);
        }, cancellationToken);
                
    }
}

Para configurar o comando para posicionamento, texto e assim por diante, não é mais necessário fornecer um arquivo .vsct. Em vez disso, é feito através de código:

public override CommandConfiguration CommandConfiguration => new("%VisualStudio.Extensibility.Command1.DisplayName%")
{
    Icon = new(ImageMoniker.KnownValues.Extension, IconSettings.IconAndText),
    Placements = [CommandPlacement.KnownPlacements.ExtensionsMenu],
};

Este código é mais fácil de entender e seguir. Na maioria das vezes, você pode escrever esta extensão puramente através do editor com a ajuda do IntelliSense, mesmo para configuração de comando.

Comparando os diferentes modelos de extensibilidade do Visual Studio

No exemplo, você pode notar que, usando o VisualStudio.Extensibility, há mais linhas de código do que o Community Toolkit no manipulador de comandos. O Community Toolkit é uma excelente camada de facilidade de uso para desenvolver extensões com o VSSDK; no entanto, existem armadilhas que não são imediatamente óbvias, o que levou ao desenvolvimento do VisualStudio.Extensibility. Para entender a transição e a necessidade, especialmente quando parece que o Kit de Ferramentas da Comunidade também resulta em um código fácil de escrever e entender, vamos analisar o exemplo e comparar o que está acontecendo nas camadas mais profundas do código.

Podemos analisar rapidamente o código neste exemplo e ver o que realmente está a ser chamado do lado do VSSDK. Vamos nos concentrar apenas no trecho de execução de comandos, já que há inúmeros detalhes que o VSSDK precisa, que o Community Toolkit esconde bem. Mas uma vez que olhamos para o código subjacente, você entenderá por que a simplicidade aqui é uma compensação. A simplicidade esconde alguns dos detalhes subjacentes, o que pode levar a um comportamento inesperado, bugs e até mesmo problemas de desempenho e falhas. O trecho de código a seguir mostra o código do Kit de Ferramentas da Comunidade desempacotado para mostrar as chamadas VSSDK:

private void Execute(object sender, EventArgs e)
{
    package.JoinableTaskFactory.RunAsync(async delegate
    {
        var textManager = await package.GetServiceAsync<SVsTextManager, IVsTextManager>();
        textManager.GetActiveView(1, null, out IVsTextView activeTextView);

        if (activeTextView != null && activeTextView is IVsTextViewEx nativeView)
        {
            await package.JoinableTaskFactory.SwitchToMainThreadAsync();
            ErrorHandler.ThrowOnFailure(nativeView.GetWindowFrame(out object frameValue));

            IComponentModel2 compService = package.GetService<SComponentModel, IComponentModel2>();
            IVsEditorAdaptersFactoryService editorAdapter = compService.GetService<IVsEditorAdaptersFactoryService>();
            var wpfTextView = editorAdapter?.GetWpfTextView(activeTextView);

            if (frameValue is IVsWindowFrame frame && wpfTextView != null)
            {
                var fileText = File.ReadAllText(Path.Combine(Path.GetTempPath(), "test.txt"));
                wpfTextView.TextBuffer?.Insert(0, fileText);    
            }
        }
    });
}

Há poucos assuntos para abordar aqui, e todos eles giram em torno de encadeamento e código assíncrono. Vamos analisar cada um deles em detalhes.

API assíncrona versus execução de código assíncrona

A primeira coisa a observar é que o método ExecuteAsync no Community Toolkit é uma chamada assíncrona envolvida de tipo fire-and-forget no VSSDK:

package.JoinableTaskFactory.RunAsync(async delegate
{
  …
});

O VSSDK em si não oferece suporte à execução assíncrona de comandos de uma perspetiva de API principal. Ou seja, quando um comando é executado, o VSSDK não tem uma maneira de executar o código do manipulador de comandos em um thread em segundo plano, esperar que ele termine e retornar o usuário ao contexto de chamada original com resultados de execução. Portanto, mesmo que a API ExecuteAsync no Community Toolkit seja sintaticamente assíncrona, ela não é uma verdadeira execução assíncrona. Pode-se chamar o ExecuteAsync repetidamente sem precisar esperar que a chamada anterior seja concluída, pois é uma forma de execução assíncrona de tipo "disparar e esquecer." Embora o Kit de Ferramentas da Comunidade forneça uma experiência melhor em termos de ajudar os extensores a descobrir como implementar cenários comuns, ele não pode resolver os problemas fundamentais com o VSSDK. Nesse caso, a API VSSDK subjacente não é assíncrona, e os métodos auxiliares de disparo e esquecimento fornecidos pelo Kit de Ferramentas da Comunidade não conseguem abordar corretamente a manipulação assíncrona e a gestão do estado do cliente; eles podem ocultar alguns possíveis problemas difíceis de depurar.

Thread da interface do usuário versus thread em segundo plano

A outra consequência com essa chamada assíncrona encapsulada do Kit de Ferramentas da Comunidade é que o código em si ainda é executado a partir do thread da interface do usuário, e cabe ao desenvolvedor da extensão descobrir como alternar corretamente para um thread em segundo plano se você não quiser correr o risco de congelar a interface do usuário. Por mais que o Community Toolkit possa ocultar o ruído e o código extra do VSSDK, ele ainda requer que você entenda as complexidades do threading no Visual Studio. E uma das primeiras lições que você aprende no threading VS é que nem tudo pode ser executado a partir de um thread em segundo plano. Em outras palavras, nem tudo é thread safe, especialmente as chamadas que ocorrem em componentes COM. Assim, no exemplo acima, você vê que há uma chamada para alternar para o thread principal (UI):

await package.JoinableTaskFactory.SwitchToMainThreadAsync();
ErrorHandler.ThrowOnFailure(nativeView.GetWindowFrame(out object frameValue));

Você pode, é claro, voltar para um thread em segundo plano após esta chamada. No entanto, como um extensor usando o Kit de Ferramentas da Comunidade, você precisará prestar muita atenção ao thread em que seu código está e determinar se ele tem o risco de congelar a interface do usuário. O threading no Visual Studio é difícil de acertar e requer o uso adequado de JoinableTaskFactory para evitar bloqueios. A dificuldade para escrever código que lida com threading corretamente tem sido uma fonte constante de bugs, mesmo para nossos engenheiros internos do Visual Studio. O VisualStudio.Extensibility, por outro lado, evita esse problema completamente executando extensões fora do processo e confiando em APIs assíncronas de ponta a ponta.

API simples versus conceitos simples

Como o Kit de Ferramentas da Comunidade esconde muitos dos meandros do VSSDK, ele pode dar aos extensores uma falsa sensação de simplicidade. Vamos continuar com o mesmo código de exemplo. Se um extensor não souber sobre os requisitos de threading do desenvolvimento do Visual Studio, ele pode assumir que seu código é executado a partir de um thread em segundo plano o tempo todo. Eles não terão nenhum problema com o fato de que a chamada para ler um arquivo de texto é síncrona. Se estiver num thread em segundo plano, ele não congelará a interface do utilizador se o ficheiro em questão for grande. No entanto, quando o código é desempacotado no VSSDK, eles perceberão que não é o caso. Portanto, embora a API do Community Toolkit certamente pareça mais simples de entender e mais coesa de escrever, porque está vinculada ao VSSDK, ela está sujeita a limitações do VSSDK. As simplicidades podem encobrir conceitos importantes que, se quem expande não entender, podem causar mais prejuízos. VisualStudio.Extensibility evita muitos problemas causados pelas dependências do thread principal, concentrando-se no modelo out-of-process e nas APIs assíncronas como a nossa base. Embora sair do processo simplificaria mais o threading, muitos desses benefícios são transferidos para extensões que também são executadas dentro do processo. Por exemplo, os comandos VisualStudio.Extensibility são sempre executados em um thread em segundo plano. A interação com APIs VSSDK ainda requer um conhecimento profundo de como o threading funciona, mas pelo menos você não pagará o custo de travamentos acidentais, como neste exemplo.

Gráfico comparativo

Para resumir o que abordamos em detalhes na seção anterior, a tabela a seguir mostra uma comparação rápida:

VSSDK Kit de Ferramentas da Comunidade VisualStudio.Extensibilidade
Suporte de Execução .NET Framework .NET Framework .NET
Isolamento de Visual Studio
API simples
Execução assíncrona e API
Amplitude do cenário VS
instalável sem reiniciar
suportaVS 2019 eabaixo de

Para ajudá-lo a aplicar a comparação às suas necessidades de extensibilidade do Visual Studio, aqui estão alguns cenários de exemplo e nossas recomendações sobre qual modelo usar:

  • Sou novo no desenvolvimento de extensões do Visual Studio e quero a experiência de integração mais fácil para criar uma extensão de alta qualidade e sópreciso darsuporte ao Visual Studio 2022 ou superior.
    • Nesse caso, recomendamos que você use VisualStudio.Extensibility.
  • Gostaria de escrever uma extensão destinada ao Visual Studio 2022 e superior. No entanto,VisualStudio.Extensibility não suporta todas asfuncionalidades de que preciso.
    • Recomendamos que, nesse caso, você adote um método híbrido de combinar VisualStudio.Extensibility e VSSDK. Você pode criar uma extensão VisualStudio.Extensibility que executa no processo, que permite acessar VSSDK ou APIs do Community Toolkit.
  • Tenho uma extensão existente e quero atualizá-la para suportar versões mais recentes. Quero que minha extensão ofereça suporte ao maior número possível de versões do Visual Studio.
    • Como o VisualStudio.Extensibility suporta apenas o Visual Studio 2022 e superior, o VSSDK ou o Community Toolkit é a melhor opção para este caso.
  • Tenho uma extensão existente que gostaria de migrar paraVisualStudio.Extensibility para aproveitar o .NET e instalar sem reiniciar.
    • Esse cenário é um pouco mais matizado, já que o VisualStudio.Extensibility não oferece suporte a versões de nível inferior do Visual Studio.
      • Se sua extensão existente oferecer suporte apenas ao Visual Studio 2022 e tiver todas as APIs necessárias, recomendamos que você reescreva sua extensão para usar o VisualStudio.Extensibility. Mas se sua extensão precisar de APIs que o VisualStudio.Extensibility ainda não tem, vá em frente e crie uma extensão VisualStudio.Extensibility que seja executada em processo para que você possa acessar APIs VSSDK. Eventualmente, pode eliminar o uso da API VSSDK à medida que o VisualStudio.Extensibility adiciona suporte e move as suas extensões para executar fora do processo.
      • Se sua extensão precisa oferecer suporte a versões de nível inferior do Visual Studio que não têm suporte para VisualStudio.Extensibility, recomendamos que você faça alguma refatoração em sua base de código. Puxe todo o código comum que pode ser compartilhado entre versões do Visual Studio para sua própria biblioteca e crie projetos VSIX separados que visam modelos de extensibilidade diferentes. Por exemplo, se sua extensão precisar oferecer suporte ao Visual Studio 2019 e ao Visual Studio 2022, você poderá adotar a seguinte estrutura de projeto em sua solução:
        • MyExtension-VS2019 (este é o seu projeto de contêiner VSIX baseado em VSSDK destinado ao Visual Studio 2019)
        • MyExtension-VS2022 (este é o seu projeto de contêiner VSIX baseado em VSSDK+VisualStudio.Extensibility destinado ao Visual Studio 2022)
        • VSSDK-CommonCode (esta é a biblioteca comum usada para chamar APIs do Visual Studio por meio do VSSDK. Ambos os seus projetos VSIX podem fazer referência a essa biblioteca para compartilhar código.)
        • MyExtension-BusinessLogic (esta é a biblioteca comum que contém todo o código pertinente à lógica de negócios da sua extensão. Ambos os seus projetos VSIX podem fazer referência a essa biblioteca para compartilhar código.)

Próximos passos

Nossa recomendação é que os extensores comecem com o VisualStudio.Extensibility ao criar novas extensões ou aprimorar as existentes e usem o VSSDK ou o Kit de Ferramentas da Comunidade se você se deparar com cenários sem suporte. Para começar, com o VisualStudio.Extensibility, navegue pela documentação apresentada nesta seção. Você também pode fazer referência ao de repositório do do GitHub VSExtensibility para amostras ou para arquivar problemas de .