sh1’s diary

プログラミング、読んだ本、資格試験、ゲームとか私を記録するところ

Visual Studio Extension 拡張機能 (VSIX) を作成する

Visual Studio拡張機能を作成したときの内容をメモします。

今回、拡張機能を作成しようと思ったきっかけは、特定のプロジェクトの obj フォルダーが時々おかしくなってしまい、「エラー一覧」などの通知に警告が表示されることがありました。

obj フォルダーの中身をすべて削除すると問題が解消されることがわかったので、拡張機能で「obj フォルダーの中身をクリーンアップする」機能を開発しておこうか、と思いました。

obj は、中間出力ディレクトリです。なので、ビルド処理の一貫として削除しても基本的には大丈夫です。(上記の Microsoft Learn にも記述があります)ビルド処理が上手くできない原因になることがあるので、その場合は削除する、というのは適切な処理だと私は思います。

Visual Studio Code の拡張を作成するときは Type Script を利用しましたが、Visual Studio のときは .NET Framework で作れるみたいですね。

Visual Studio 拡張機能

Visual Studio拡張機能は、Visual Studio SDK というものをインストールするところからスタートします。現在は、Visual Studio をインストールすると Visual Studio Installer がついていますので、対象の Visual Studio の「変更」を選択し、他のツールセットの項目から「Visual Studio 拡張機能の開発」を選択してインストールします。

2023 年 9 月現在、Visual Studio拡張機能の開発は残念な点があって、プログラムを作るフレームワーク .NET Framework であることです。.NET (Core) になっていません。なので、新しい C# の機能をガンガン使っていくことができないようです。(違ったらすいません)

これは、Visual Studio 自体が .NET にアップデートされないと解消しないと思われます

そんなわけで、.NET Framework でプログラムを書く気持ちが整ったらプロジェクトを作成します。作成するにあたって参考にしたのは、Microsoft Learn の「チュートリアル」です。

拡張機能の開発デバッグ

拡張機能デバッグは、Visual Studio 自体が起動します。コードを書いている Visual Studio と(もっとも)違う点は、開発中の拡張機能がインストールされている、ということです。

Visual Studio のメニュー「拡張機能」から「拡張機能の管理」を選択して「インストール済み」の項目を確認してみてください。開発プロジェクトと一致する拡張機能が追加されているはずです。(これが無いとデバッグできない)

あとは、拡張機能を動作させると、コードを書いている Visual Studio 側のブレークポイントなんかが機能してデバッグすることができる、といった感じになっていました。ここまでは、簡単でやさしいと思います。

言語も切り替えできるように追加インストールが必要です。オプションから設定。

プロジェクトの構成について

作成したソリューション(プロジェクト)のそれぞれのファイルの役割を確認しておきます。

ざっくりと、Visual Studio 拡張機能の開発に関するファイルと、拡張機能のインストールに使用するファイルで分けて考えます。補足すると、VSIX は Open Packaging Conventions (OPC) 標準なので、ZIP ファイルとして開くことができる。

  • プロジェクト名+Package.vsct
    • コマンド定義ファイル。Visual Studio のどこに拡張機能を追加するのか定義される。(ボタン、メニュー、キーボードなのかなど)
  • source.extension.vsixmanifest
    • メインファイル。VSIX に関するところの、つまりインストール関係。(拡張機能の作成者、バージョンなどの情報はここ)
  • プロジェクト名+Package.cs
    • 拡張機能のエントリポイントになるクラス。(ただし拡張機能を作る際はほとんど参照しないはず)

コマンドを追加する

メニューに新しいコマンドを追加したい場合は、.cs ファイルを追加するのと同じで「追加」>「新しい項目」を選択します。Extensibility の項目から Command.cs を選択します。

ファイルを追加すると .vsct ファイルにメニューに表示されるコマンドのテキストがあります。以下だと Invoke ObjCommand がメニューに表示されます。

    <Buttons>
      <Button guid="guidObjBinCleanerPackageCmdSet" id="ObjCommandId" priority="0x0100" type="Button">
        <Parent guid="guidObjBinCleanerPackageCmdSet" id="MyMenuGroup" />
        <Icon guid="guidImages" id="bmpPic1" />
        <Strings>
          <ButtonText>Invoke ObjCommand</ButtonText>
        </Strings>
      </Button>
    </Buttons>

コマンドのローカライズ

コマンドのテキストをローカライズ(多言語対応)させるためには .vsct ファイル自体をn個に増やします。例えば、以下のようになりました。

  • VsPackage.en-US.vsct
  • VsPackage.ja-JP.vsct

加えて、ローカライズ用のリソースファイルもどういうわけか必要になりました。

  • TextResources.resx
  • TextResources.en-US.resx
  • TextResources.ja-JP.resx

テキスト用のリソースファイルだけ、fallback 用の言語ファイルが必要でした。コーディング時にも参照しているデフォルトなので必要みたいです。

ちなみに、fallback は、部分的にローカライズされた言語に対して、リソースがローカライズされていないときに利用されます。ローカライズの指定は、結構ややこしくて、まず、AssemblyInfo.cs に以下の追加が必要です。

// ローカライズされたリソースを利用する設定
[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]

この設定で、en-US がデフォルトになります。そのため、TextResources.resx は、名前を定義する必要こそありますが(コンパイルのため)、値を設定しなくてもよいです。値は、en-USja-JP 言語用ファイルで設定します。

.csproj ファイルを編集します。

  <ItemGroup>
    <EmbeddedResource Include="Properties\TextResources.resx">
      <Generator>ResXFileCodeGenerator</Generator>
      <LastGenOutput>TextResources.Designer.cs</LastGenOutput>
      <LogicalName>$(RootNamespace).Properties.TextResources.resources</LogicalName>
    </EmbeddedResource>
    <EmbeddedResource Include="Properties\TextResources.en-US.resx">
      <MergeWithCTO>true</MergeWithCTO>
      <LogicalName>$(RootNamespace).Properties.TextResources.en-US.resources</LogicalName>
    </EmbeddedResource>
    <EmbeddedResource Include="Properties\TextResources.ja-JP.resx">
      <MergeWithCTO>true</MergeWithCTO>
      <LogicalName>$(RootNamespace).Properties.TextResources.ja-JP.resources</LogicalName>
    </EmbeddedResource>
  </ItemGroup>

LogicalName を雑に設定してはいけません。プロジェクトの構成と一致するようにします。上記は Properties フォルダにリソースを設置していた例です。末尾だけ resources に変更しています。

正しく設定できていないときは MissingManifestResourceException が発生します。

公開する

Visual Studio Marketplace に公開をする。これは Visual Studio Code の拡張を公開したときと同じ手順だと思います。

出力した .vsix ファイルをアップロードして公開すればいい。

Internal Name は、URL の最後に影響するが以下のとおりになっていた。

アップロードしたあとはデフォルトだと非公開状態なので public にすれば Visual Studio の「拡張機能」から検索できるようになる。便利。

GitHub

今回作成した拡張を公開しています。

参考