新しもの好きプログラマの耳より情報ブログ

仕事でもあるプログラミングについて役に立ちそうな情報を発信していこうというブログです。役に立たなそうな情報はfacebookで。

C#でのファイルパスの重複排除(大小文字無視)は、Dictionaryなどのコレクションにやらせると便利

概要

Windowsのファイルパスなどで、大文字小文字を無視して文字列比較をしたい場合があります。C#では、単純な比較もできますし、ソートと一致判定を伴うコレクション(HashSet,Dictionaryなど)でもそうした比較を行うことができます。ただし比較方式にはいくつかの選択肢があります。これらについて、意外とちゃんと使えていないコードを見かけるので、小ネタですが記事を書きました。

結論

最初に結果だけ言うと、このように書けば、大文字小文字を無視して比較し重複排除するリストができます。これを見て「常識だろ」と思う人も多いと思いますが、意外に「何それ?!」という人もいます。

HashSet<string> filePaths = new(StringComparer.OrdinalIgnoreCase);
var add = @"C:\FilePath1"; //追加するファイルパス
filePaths.Add(add);

よく見るコードとその改善

使えていないというのは、.NETがやってくれることを次の例のように自力でやってしまうということです。意外に、次のようなコードはよく見かけます。(そういうコードではLINQも使っていないことが多いですが、この話と無関係なので使うサンプルにします)

使えていない例

List<string> filePaths = new();
var add = @"C:\FilePath1"; //追加するファイルパス
if (filePaths.All(d => d.ToUpper() != add.ToUpper()))
{
    filePaths.Add(add);
}

大小文字無視の比較のためにToUpper()を使い、既存のListに含まれていない場合はListへ追加しています。

もちろんこのコードでも動くとは思いますが、次のように.NETにやってもらった方が保守性も、おそらくは性能も上がります。

改善1:大小文字無視の比較はEqualsメソッドで

ToUpper()で頑張らなくても、文字列のEqualsメソッドは、大小文字無視の比較ができます。

List<string> filePaths = new();
var add = @"C:\FilePath1"; //追加するファイルパス
if (filePaths.All(d => d.Equals(add, StringComparison.OrdinalIgnoreCase)))
{
    filePaths.Add(add);
}

改善2:コレクションの重複は、コレクションに判定させる

重複排除したリストを作りたいなら、HashSetが使えます。HashSetは、コンストラクタに指定することで大小文字無視の比較ができます。

HashSet<string> filePaths = new(StringComparer.OrdinalIgnoreCase);
var add = @"C:\FilePath1"; //追加するファイルパス
filePaths.Add(add);

Dictionaryでも使えます

Dictionaryのキーの判定にも同じ方法を使えます。それ以外にもConcurrentDictionary等のソート・一致判定を伴うコレクションなら、おそらく全て使えます。

Dictionary<string, int> filePathAndValues = new(StringComparer.OrdinalIgnoreCase);
//追加するファイルパスと値
var addFilePath = @"C:\FilePath1";
int addValue = 1;
filePathAndValues.Add(addFilePath, addValue);

StringComparerの選び方

StringComparerというのが出てきましたが、大小文字無視(IgnoreCase)のものだけでも3種類あります。どれを使ったら良いでしょう。これも割と、書く人によってバラバラだったりします。

Windowsのファイルパスの判定が目的なら、StringComparer.OrdinalIgnoreCaseを使いましょう。大文字小文字の差を無視した上でのシンプルなバイナリ比較なので、ファイルパスのような一致判定には適しています。

悩む人が多かったのか、Learnにまとまっていました。ここでもやはり、ファイルパスなどはOrdinalIgnoreCaseが推奨されています。

.NET での文字列の比較に関するベスト プラクティス - .NET | Microsoft Learn

まとめ

単純なファイルパスの一致判定と重複排除だけの話ですが、ちゃんと.NETのライブラリを使うかどうかによって、コードの見やすさもおそらくは性能もだいぶ変わって来ます。こうしたことの積み重ねが、意外と保守性や性能に効いてくることもあると思うので、知らなかった人はぜひ使ってみてください。知っていた人は、周りに分かっていない人がいたらぜひ教えてあげて欲しいなと思います。

AzureでWindowsアプリのCI/CD環境を作る:DevDriveを組み合わせてみる

概要

開発者の手元のビルド環境だけではなく、どこでもリリースビルドが再現可能であること。 Warningの作り込みやUTコードのFailを早めに見つけてデグレを防ぐこと。 こうした、CI/CD的な考え方は、Windowsアプリ開発においても大事なところだと思います。(現場であまり重視されていない感もありますが・・・)

方法はいくつもあると思いますが、Azure Pipelines (DevOps)とAzure VMで実現できます。そのあたりの話はまた別の記事で書こうと思います。

この記事の話は

Windows 11の新機能として登場したDevHomeやDevDriveなどは、もちろん開発環境の量産にも使えますが、実はCI/CDを大きく助けてくれる機能にもなりそうです。

今回は、その中でもDevDriveが導入できるかを試してみて、効果を見ていこうと思います。

結果まとめ

説明が長くなるので、最初に結果をまとめます。こんな感じの内容を説明していきます。

  1. Azure Pipelinesのビルド環境にDevDriveを組み込むこと自体は難しくない
  2. むしろWin11-23H2へのDevDriveの導入で、Defenderのアップデートが必要なのが罠だった
  3. 中でも、一時ディスクDドライブにコマンドで作成する方法が、低コストで使いやすいと思ったので、それを紹介
  4. 効果はいまいちで、ビルド時間が数%減るか減らないかくらい
  5. むしろCPU強化のほうが効果があったので、そのあたりがネックにならないリッチなビルド環境向けかも
  6. 少なくともマイナスではなかったので、ダメ元で導入してみても良いと思う

環境の作り方

Azure Pipelinesのビルド環境にDevDriveを組み込む方法について、注意点なども触れながら順番に紹介します。

DevDriveの導入

DevDriveは、ディスク上のパーティションを割り当てるか、もしくはvhdファイルを新規作成してDevDriveにするという作り方をします。注意点として、最低サイズが50GBです。50GB以上の空きパーティションか空き領域があれば、作成できるということになります。

とりあえず導入するための手順であれば、MSのドキュメントがちゃんとまとまっていますので、そちらを見るのが良いです。

Windows 11 で Dev Drive を設定する | Microsoft Learn

DevDriveのウイルススキャンの影響を減らす

DevDriveのパフォーマンスが高いとされる理由の一つに、Defenderのウイルススキャンの影響を減らせるという点があります。「パフォーマンスモード」と呼ばれているようですが、ウイルススキャンを遅延させることで速度を上げるようです。これを使わないと、DevDriveのメリットは半減以下です。

幸い、DevDriveを作るだけで、デフォルトでパフォーマンスモードの対象になりました。この点は分かりやすいです。

意外な注意点

Defenderのアップデートが必要です。

Windows11 23H2を新規インストールすると、Defenderのバージョンが古くて、DevDriveパフォーマンスモードの条件を満たしません。2023/12前半時点では、2回くらい手動でアップデートを促してやらないと、条件を満たすバージョンになりませんでした。見た目にはDevDriveになっているので気付きづらく、今のところ注意が必要です。

参考:パフォーマンス モードを使用して Dev Drive を保護する | Microsoft Learn

DevDriveとAzure Pipelinesの組み合わせ方

まず、Azure PipelinesのSelf Hosted Agent(長いので、以下Agent)でビルドする時は、こんな感じになります。

この「ローカルの一時フォルダ」を、DevDriveにすれば良いことになります。幸い、Agentはセットアップする時に一時フォルダの場所をフルパスで指定できます。パスが存在していなくてもAgentは起動できるので、あとからDevDriveを作成・マウントしても問題ないので、このあとに出てくる一時ディスクを使えます。

Azure VMの一時ディスクがお勧め

Azure VMには、OSディスクとデータディスクの他に、一時ディスクというものがあります。名前の通りでVM停止時などにデータが失われる可能性がありますが、必ずOSと同じハードに物理接続されているディスクであり、元々ページファイルなどを置く想定のディスクなので速度が期待できます。また、VMの料金に含まれています。

この特性は、一時的に使うだけのビルドPCの用途にはぴったりです。ただし、データが失われた場合はDevDriveを作り直す必要があります。これを毎回手動でやるのは厳しいですが・・・スクリプトで実現できました。次にそのやり方をまとめます。

一時ディスク上のDevDriveを使った、ビルド環境の運用方法

ここまでの内容を踏まえて、次のようにビルド環境を構築・運用できます。

  1. Agentをセットアップして、一時フォルダのパスを存在しないドライブにしておく(「V:\AgentWork」など)
  2. 「一時ディスク(Dドライブ)へvhdファイルを作成・マウントしてDevDriveに設定し、1で設定したドライブレターを与える」というスクリプトを作成し、タスクスケジューラの「スタートアップ時」イベントに登録する

これで、VMを起動するだけで使える、DevDriveでのビルド環境のできあがりです。次の図のようなものが実現できました。

スクリプトについては、DevDriveの設定はFormatコマンドで行えるので、DiskPartと組み合わせて次のバッチファイルで行けます。

https://gist.github.com/suusanex/67610d6d8a7cfd7abca6e0e3108507d8

効果は?

色々試しましたが、少しは速くなるもののあまり効果はない印象です。続けて詳しく書いて行きます。

効果の測定

測定のやり方

次の条件で、実際にパイプラインを動かして所要時間を測定していきます。

  • 測定対象
    • 1,C#C++(WDK)の小規模ソリューション(非公開・手元にあったもの)
    • C#部分は発行(publish)、WDK部分はビルド
    • 2,OrchardCore
    • MSのブログで、DevDriveのパフォーマンス例で22%改善としていたもの
    • VisualStudio2022で、ソリューション構成Release/Any CPUをビルド
  • 測定条件(組み合わせて測定する)
    • VMのサイズ
      • Standard_D4ds_v5
      • Standard_F8s_v2
    • ストレージ
      • 一時ディスク(Dドライブ)
      • データディスク Premium SSD
    • DevDrive
      • パーティション全体をDevDriveに設定(Premium SSDのみ)
      • vhdファイルを作成してマウントし、DevDriveに設定(一時ディスクのみ)
      • DevDrive無し(通常のNTFS
  • 測定方法
    • 2~3回ほどパイプラインを実行して、パイプライン実行結果に記録された所要時間を見る
      • 対象条件での初回の実行結果は捨て、2回目以降の実行結果を採用する
      • あまり効果が見られないため、途中から一部組み合わせの測定をカットしたり回数を減らしたりしている

測定結果

条件別に、次の対象の測定結果を表にまとめます。測定結果の単位は全て秒です。

  • 対象1:測定対象1のcheckout
  • 対象2:測定対象1のC#ソリューション発行
  • 対象3:測定対象1のWDKビルド
  • 対象4:測定対象2のNuGet復元
  • 対象5:測定対象2のソリューションビルド

Azure VM Standard_D4ds_v5

データディスクでは、ほとんど差が無いという印象です。一時ディスクのほうでは、数%くらいはDevDriveのほうが優れてるかも・・・?というところでしょうか。動きを見ているとCPUが一番ネックになっていそうにも見えるので、CPUが強そうなサイズに変えてみます。

ストレージ 対象1 対象2 対象3 対象4 対象5
データディスク NTFS 8~20 53~61 10 19~20 98~108
データディスク DevDrive 7~12 53~60 10~11 19~23 103~111
一時ディスク NTFS 10~19 57~62 11~13 - -
一時ディスク DevDrive 7~10 54~58 10 - -

Azure VM Standard_F8s_v2

もう少し効果が明確になった印象でしょうか。せいぜい5%がいいところですが、確かに改善はしているように見えます。もしかして、IOPS制限などAzure VM固有の要素に影響されているのかもしれないので、ローカルのHyper-Vも試してみます。

ストレージ 対象1 対象2 対象3 対象4 対象5
データディスク NTFS 9 78 13 24 85~100
データディスク DevDrive 9~11 68~75 13 24~27 91~95
一時ディスク NTFS - - - 25~28 92~100
一時ディスク DevDrive 15 73 13 24~26 84~95

Hyper-V(あまり性能は良くない)

適当に確保したローカルのHyper-V環境での測定です。さほど性能が良いものではありません。こちらでは10%程度の改善はしているように見えます。Azure VM固有の要因が悪いと言えるほどのデータではありませんが、条件によっては改善幅が大きくなるものと期待はできます。

ストレージ 対象1 対象2 対象3 対象4 対象5
システムドライブ NTFS - - - 22~24 100~112
システムドライブ上のvhd DevDrive - - - 24~28 89~108

考察

MSのブログで出ているような22%もの改善は得られませんでした。とはいえ、ストレージが大きなネックになっている環境では、5~10%程度の改善は出ているように見えます。当たり前といえばそうですが、ストレージ以外がネックになっている場合は効果は出づらいようです。もしかするとDevDriveは、もっと各種スペックが高くてストレージだけがどうしてもネックになる、というようなリッチなビルド環境でこそ力を発揮するのかもしれません。

いずれにしても、「DevDriveにしたら、かえって悪化した」ということはなく、使って損はないようです。

Azure VMにおいては、高価なPremium SSDを使っても一時ディスクと速度に大差はありませんでした。追加コストを支払ってデータディスクを追加するよりも、一時ディスクを使うほうが良さそうです。

まとめ

Azure VM&Azure Pipelinesを使ったビルド環境に、DevDriveを組み込むことができました。 また、スクリプトで自動作成することで、Azure VMの一時ディスク上へDevDriveを作ることができるので、現状のVMのサイズによっては追加のコスト負担無しで使うことができます。

DevDriveを使用した場合には、22%という大きな改善効果こそ見られなかったものの、使って損はない程度の改善は見られました。一時ディスク上に確保してとりあえず使ってみる価値はあるように思います。色々な条件に左右されるので、もしかして用途によっては大きな改善を得られるかもしれません。

.NET 8のプロセス間通信には、gRPCのパイプ通信が優秀なようです

概要

Windowsアプリを作成していると、PC内のプロセス同士で通信をしたい場合があります。その実装方法として、.NET 8で公式サポートされたgRPCのパイプ通信が優秀なようです。

それについて次のスライドで、イベントで登壇させてもらいました。

www.docswell.com

その内容を、記事にまとめました。具体的なコードなどは、スライドよりも詳しい情報になっていると思います。

従来のgRPCのTCP通信との比較や移行について、次の流れで紹介します。 (以下、プロセス間通信をIPCと略記します)

  1. 話の前提:IPCを使いたいケースと、gRPCでのIPC
  2. 標準のTCP通信をパイプ通信に移行
  3. そのメリットと実測データ

結論

説明が長くなるので結論だけ先に書いておくと、こんな感じになります。

  • 速度メリットとしては、ストリームを作らずに接続・切断を繰り返すケースではかなり速い。速度以外のメリットもある。デメリットは無さそう。
  • 既存のgRPC(TCP通信)からの移行も容易
  • gRPC自体の使いやすさは変わらずにメリットを得られるので、.NET 8でIPCをするのなら第一選択肢だと思う
  • 実装は割と簡単だが、用途によってはパイプのACL設定をWin32 APIで変更する必要がある点に注意

話の前提

IPCを使いたいケース

Windowsアプリでは、プロセス1つで機能を完結できず、他のプロセスと通信したいケースがあります。主に、動作するセッションが異なる場合が多いと思います。

例えば次の図のように、バックグラウンドで動くWindowsサービスと、GUIを持つユーザーセッションアプリで通信したい場合などです。

.NET8でgRPCのパイプ通信を評価してみる&ついでに既存プロジェクトを.NET8へ移行_図.png

gRPCでのIPC

この用途で、gRPCを使うことができます。gRPCとは、専用の構文で通信内容を定義しておくことで、クロスプラットフォームの通信ができる方式です。かなり有名だと思うので、ここでは詳しくは触れません。

.NET8でgRPCのパイプ通信を評価してみる&ついでに既存プロジェクトを.NET8へ移行_図-1.png

そうした方式ですが、同一プラットフォーム内でIPCする今回の用途でも、十分にメリットがあります。通信内容と通信方式が分離されていて、かつ通信方式部のコード生成も手厚いことから、他のIPC方式よりもかなり手軽に使えます。速度としても、localhostへのTCP通信となるので、十分に速いです。

標準のTCP通信をパイプ通信に移行

パイプ通信にできる

TCPでも十分に速いと書きましたが、とはいえWindows内のIPCであればTCP通信よりもパイプ通信の方が速いはずです。.NET 8で、パイプ通信が公式サポートされました。gRPCの特徴から、通信内容の定義はそのままにして、通信方式だけをパイプ通信へ変えることが出来ます。

.NET8でgRPCのパイプ通信を評価してみる&ついでに既存プロジェクトを.NET8へ移行_図-2.png

パイプ通信にするメリット

TCP通信と比べてのメリットは、このようなものがあります。

  1. 他ソフトとのポート番号の衝突を気にしなくていい
    • パイプは文字列なので衝突する可能性は低い
  2. ファイルと同様に、WindowsACLでの権限管理ができる
  3. TCPでも十分に速い」とは言ったが、より高速な用途ではパイプが優れるはず

デメリットは標準のTCPよりも実装に少し手間が増えるくらいで、他は特に見当たりません。

そのメリットと実測データ

パイプのほうが速いはずだと言ってきましたが、具体的にどういうケースでどれくらい速いのか。実測してみました。

接続・通信・切断を繰り返すケース

100ms間隔で「接続・送信・受信・切断」を繰り返し、1回ごとの所要時間の中央値を計測しました。

.NET8でgRPCのパイプ通信を評価してみる&ついでに既存プロジェクトを.NET8へ移行_図-3.png

結果は・・・

  • TCP:約2000ms/1回
  • パイプ:約1ms /1回

おそらくTCPの接続のハンドシェイク等の時間かと思いますが、かなり大きな差が付きました。数秒オーダーよりも頻繁に通信を繰り返す場合、パイプ通信が有効と言えます。

ストリーム通信を確立し、その上で通信を繰り返すケース

ストリーム通信を確立した上で、その上で10ms間隔での通信を繰り返し、次の内容を計測しました。

  1. ループ1回ごとの所要時間の中央値
  2. 送信元の通信データ作成から、送信先の通信データ取得までの所要時間の中央値

.NET8でgRPCのパイプ通信を評価してみる&ついでに既存プロジェクトを.NET8へ移行_図-4.png

結果は・・・

  1. ループ1回の所要時間
    • TCP:約15.65ms
    • パイプ:約15.55ms
  2. 送信元の通信データ作成から、送信先の通信データ取得までの所要時間
    • TCP:約0.25ms
    • パイプ:約0.15ms

通信繰り返しの所要時間はほぼ同じで、通信内容そのものの到達時間にはわずかに差がありました。と言っても、通信全体の所要時間からすると誤差に近いです。

よほど通信遅延のタイミングにシビアな用途でなければ、差は無いと言って良いと思います。

具体的な実装方法やポイントの紹介

gRPC(TCP)でのプロセス間通信を行うコードがすでにある前提で、これをパイプ通信に変更するところが対象です。

とにかくコード全体を見たい人へ

この記事で説明しているコードは、次の場所のソリューションの中の、ポイントとなる部分です。全体を見たい人はそちらをどうぞ。 https://github.com/suusanex/sample_winservice_pipe_duplex_wcf_and_grpc/tree/master/gRPCWinServiceSample

クライアント側

サーバーへ接続する部分は、TCPならば1行で書けますが、パイプ通信の場合は少し複雑になります。

TCPの場合はこのように接続できます。

var channel = GrpcChannel.ForAddress("http://localhost:50100/Connect1");

パイプ通信の場合は、次のように独自のConnectCallbackを作成する必要があります。

var connectionFactory = new NamedPipesConnectionFactory("gRPCWinServiceSamplePipeName");
var socketsHttpHandler = new SocketsHttpHandler
{
    ConnectCallback = connectionFactory.ConnectAsync
};
m_Channel = GrpcChannel.ForAddress("http://localhost/Connect1", new GrpcChannelOptions
{
    HttpHandler = socketsHttpHandler
});

ConnectCallbackを実装している「NamedPipesConnectionFactory」は組み込みのクラスではなく、独自に実装する必要があります。次のようになります。ここで、パイプの細かい設定を変更することができます。

public class NamedPipesConnectionFactory(string m_PipeName)
{
    public async ValueTask<Stream> ConnectAsync(SocketsHttpConnectionContext _,
        CancellationToken cancellationToken = default)
    {
        var clientStream = new NamedPipeClientStream(
            serverName: ".",
            pipeName: m_PipeName,
            direction: PipeDirection.InOut,
            options: PipeOptions.WriteThrough | PipeOptions.Asynchronous,
            impersonationLevel: TokenImpersonationLevel.Anonymous);

        try
        {
            await clientStream.ConnectAsync(cancellationToken).ConfigureAwait(false);
            return clientStream;
        }
        catch
        {
            await clientStream.DisposeAsync();
            throw;
        }
    }
}

実装する量は少々多いですが、つまりはGrpcChannel.ForAddressで接続をしている部分のコードだけ置き換えれば、TCP通信をパイプ通信に変えることが出来ます。

GitHubのコードで言うと、このあたりこのクラスになります。

クライアント側についてはおおむね、次のMS Learnの記載通りであり、特に注意点はありません。 gRPC と名前付きパイプを使ったプロセス間通信 | Microsoft Learn

サーバー側

サーバー側は、GenericHostを使っていることを前提に説明します。GenericHostについてはこの記事では触れませんが、DI・ロガー・各種サービスホストなどをまとめて取り扱える.NETの仕組みで、Windowsサービスもこれを使って作っていくのがお勧めです。

その前提だと非常に簡単で、TCPだと次のようにするところを

.ConfigureWebHostDefaults(webBuilder =>
{
    webBuilder.ConfigureKestrel(options =>
    {
        options.Listen(IPAddress.Loopback, 50100, listenOptions =>
        {
            listenOptions.Protocols = HttpProtocols.Http2;
        });
    });

次のようにListenNamedPipeへ置き換えるだけです。

.ConfigureWebHostDefaults(webBuilder =>
{
    webBuilder.ConfigureKestrel(options =>
    {
        options.ListenNamedPipe("gRPCWinServiceSamplePipeName", listenOptions =>
        {
            listenOptions.Protocols = HttpProtocols.Http2;
        });
    });

置き換えずに両方を記載すると、TCPとパイプの両方を待ち受けることができます。サーバー側は、かなり簡単に実装可能だと思います。

GitHubのコードで言うと、このあたりになります。

ただし、サーバー側には注意点があります。

注意点:パイプのACL

Windowsサービス(LocalSystem権限)からパイプを作ると、デフォルトではユーザー権限の書き込み不可になります。

それはそれで良いのですが、もしユーザーセッションからユーザー権限で操作したい場合は、権限を追加する必要があります。gRPC側からそれを設定できるインターフェースが有るといいのですが、見つかりませんでした。

Win32 APIを使って、パイプを開いてACLを設定すれば、実現できます。C#でも書けると思いますが、C++/CLIで書きました。記事の中に貼るにはWin32 APIを使ったコードは長すぎるので、ポイントを説明した後にGitHubのリンクを貼ります。

  1. パイプを開く
  2. 設定したいユーザーグループのSIDを文字列で指定
  3. パイプのセキュリティ設定を取得して、SIDに対する許可を設定

以上のような処理をします。最初の「パイプを開く」処理は、GenericHost側でパイプが作成されるまで成功しません。別スレッドで成功するまでリトライするなどの工夫が必要になります。

GitHubのコードは、このあたりになります。

ここまでが、TCPのgRPC通信コードを、パイプ通信に変更する方法です。

.NET8より前の既存プロジェクトからの移行は?

その前に、.NET 8へ移行しなければ、gRPCのパイプ通信は使用できません。幸い、既存プロジェクトを.NET 8に移行するのは、割と簡単です。そこも少し説明します。

WPF・クラスライブラリなど(C#)

プロジェクトの「ターゲットフレームワーク」で「.NET 8.0」を選択するだけで、たいていは動きます。もちろんファイルの<TargetFramework>を直接書き換えてもOKです。GitHubのコードではこの部分です。

C++/CLI

プロジェクトの「.NETターゲットフレームワーク」で「.NET 8.0」を選択するだけ、と書きたいところですが・・・選択肢にありません。

GUIではコンボボックスになっていますが手入力可能なので、「net8.0」と書けばOKです。プロジェクトファイルの直接書き換えも可能で、GitHubのコードではこの部分です。

まとめ

PC内でのプロセス間通信について、gRPCのパイプ通信はTCP通信よりも優れた選択肢のようです。速度面では悪くとも同等、ストリームを使わずに接続・通信・切断を繰り返すようなケースであれば大きく優れます。それ以外の運用上のメリットもあります。また、すでにTCP通信で実装している場合にも、通信内容の実装に影響することなくパイプ通信への置き換えができます。

また、記事にしてみるとそこそこ長い気がしますが、ソースコードの視点ではさほど大きな変更をすることなく、gRPCをTCPからパイプ通信に変更できます。

以上より、新規実装・既存の置き換えのどちらでも、gRPCのパイプ通信は第一選択肢になると思います。これ自体が .NET 8への移行の動機にもなるかもしれません。

既存のC++/CLIプロジェクトを、.NET 8 RC2でビルドするために必要なこと

結論

プロジェクト設定の.NETターゲットフレームワークのコンボボックスに、手入力で「net8.0」と書く。コンボボックスの選択肢には出てこない。

愚痴

C#プロジェクトは当然のようにエディタから設定できるのに、C++/CLIプロジェクトはRC2に至ってもこの状況・・・さすがに扱いがひどいのでは。

経緯など

.NET 8のリリースも近いので、.NET 6で動いているソリューションを全部.NET 8RC2にして上手く動くか、試してみようと思った。

C#のプロジェクトを開いて、GUIから.NETターゲットフレームワークを.NET 8へ変更。

つづけてC++/CLIのプロジェクトを開いて.NETターゲットフレームワークを・・・開いても、選択肢に.NET 8が無い。次のような選択肢になっている。

  • .NET 6.0
  • .NET 7.0

設定方法を探るため、新規のC++/CLIプロジェクトを作成して確認してみたところ、GUI上の表記が「net8.0」となっていた。つまり・・・

  • .NET 6.0
  • .NET 7.0
  • net8.0 (new!)

確かにプロジェクトファイル上はその表記だが・・・扱いが雑すぎでは?

既存のプロジェクトにも手入力で「net8.0」と入力することで、無事.NET 8でのビルドに成功。

指定した構成のVisualStudioをコマンド一発でインストールする方法(WinGet構成ファイル(プレビュー版))

WinGetのプレビュー機能「WinGet構成ファイル」を使うと、VisualStudioの構成ファイルインポートのような少し複雑なセットアップも自動化できる。

MSのサイトに必要な情報は一通り書いてあるが、情報量が多いので「最低限これをやりたい」という時の手順がちょっと分かりづらい。

というわけで、「Visual Studio 2022を、.vsconfigファイルで構成を指定してインストールする」だけの最低限の手順をこの記事にまとめる。Windows 10 22H2環境で動作確認済み。(DevHomeと違って、Win10でも実行可能)

次の順で説明する。

  1. 構成ファイルを作成する手順
  2. インストールする環境での手順

1の手順を一度終えてしまえば、あとはインストールしたい環境で2の手順を実行すれば良い。

構成ファイル作成側の手順

  1. configuration.dsc.yamlという名前のファイルを作成する。
  2. ※1の内容を書いて保存する。

※1

# yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/0.2
properties:
  resources:
    - resource: Microsoft.WinGet.DSC/WinGetPackage
      id: vsPackage
      directives:
        description: Install Visual Studio 2022 Enterprise
        allowPrerelease: true
      settings:
        id: Microsoft.VisualStudio.2022.Enterprise
        source: winget
    - resource: Microsoft.VisualStudio.DSC/VSComponents
      dependsOn:
        - vsPackage
      directives:
        description: Install required VS workloads
        allowPrerelease: true
      settings:
        productId: Microsoft.VisualStudio.Product.Enterprise
        channelId: VisualStudio.17.Release
        includeRecommended: true
        vsConfigFile: '${WinGetConfigRoot}\.vsconfig'
  configurationVersion: 0.2.0

簡単に内容を説明する。

  1. 1行目に、作成するファイルの形式を書く。今回はVer.0.2
  2. 2行目以降に、インストール対象のリソースを記載する。ここでは、まずwingetでVS2022Ent.をインストール。その次に.vsconfigを使ってコンポーネントをインストール。という2つ。.vsconfigファイルは、wingetを実行するフォルダに置くものとする。('${WinGetConfigRoot}.vsconfig')
  3. Ver.0.2なので、最後の行に「configurationVersion: 0.2.0」を記載する(JSONとして成立していれば最後の行にする必要は無い)

このようになっているので、このファイル1つで、まっさらな環境に対して指定した構成のVisualStudioをインストールできる。

インストールする環境での手順

初回のみ

  1. WinGetのプレビューバージョンをインストール
  2. コマンドプロンプトwinget settings を入力するとjsonファイルが関連付けで開かれるので、notepadなどを選んで開く
  3. ※2のように追記して保存して閉じ、「WinGet構成ファイル」プレビュー機能を有効にする

※2

追記する内容:

    "experimentalFeatures": {
        "configuration": true
    },

変更前(例):

{
    "$schema": "https://aka.ms/winget-settings.schema.json",

    // For documentation on these settings, see: https://aka.ms/winget-settings
    // "source": {
    //    "autoUpdateIntervalInMinutes": 5
    // },
}

変更後:

{
    "$schema": "https://aka.ms/winget-settings.schema.json",

    // For documentation on these settings, see: https://aka.ms/winget-settings
    // "source": {
    //    "autoUpdateIntervalInMinutes": 5
    // },
    "experimentalFeatures": {
        "configuration": true
    },
}

インストール

  1. 作成済みのconfiguration.dsc.yamlと使用する.vsconfigファイルを同じフォルダへ置く
  2. コマンドプロンプトを開いて、上記ファイルを置いたフォルダをカレントにする
  3. コマンド winget configure -f configuration.dsc.yaml を実行
  4. 長めの英文で警告が出るので、問題なければyを入力して実行(大雑把に言うと「構成ファイルの通りにインストールするからな。MSは責任取らないぞ。良いか?」という趣旨)

あとは待つだけ。

Air Link(Meta Quest)が繋がらない場合に、意外と見落としているポイント

Quest 2のAir Linkが繋がらない!なぜだ!もうケーブルはいらないんだ!

まずは、このあたりを試す。たいていはこれで解決する。

  1. PCとQuestとWiFiルーターを再起動する
  2. PCのOculusアプリとビデオカードのドライバ、それとQuestを最新版にアップデートする

それでもダメ?それなら、私と同じ事を見落としているかもしれない。

PCのセキュリティソフトのファイアウォールをOFFにして試す。これで上手くいったら、犯人は確定。

ファイアウォールへどのように穴を空けるかは各自の判断だが、私の環境では次のプロセスの通信を許可することで解決した。

C:\Oculus\Support\oculus-runtime\OVRServer_x64.exe

解決方法をWebで調べてみても、意外とファイアウォールというのは出てこない。盲点だった。

GitHub CopilotがIntelliSenseの進化形すぎて手放せなくなりそう

GitHub Copilotを使ってみたら、思った以上に即効性のある便利さだった。次のスライドで、いくつか実例を交えて紹介したので、興味のある人は覗いてみてほしい。

www.docswell.com