前回、MetroRadiance
をForkし、いくつかの修正を加えて、MetroRadiance.Fork
としてリリースしたことを投稿しました。今回は、それを拡張します。
MetroRadiance.Forkライブラリー
前回の投稿で、MetroRadiance
2.4をベースにした、MetroRadiance.Fork
2.5をnugetに公開したことを報告しました。ソースコードもgithubで公開しています。
このフォーク版MetroRadiance.Fork
では、本家の実装から名前空間・クラス名・メソッド名・アセンブリ名などは変更していません。そのため、今まで本家のMetroRadiance
ライブラリーを使っていた場合は、ソースコードの修正は必要ありません。ライブラリーを置き換えるのみで使用できます。
欲しかった機能
MetroRadiance.Fork
2.5.0では、MetroRadiance
2.4.0をベースに、MetroRadiance
のgithubに登録されていたIssuesやPull Requestsで、容易に取り込むことができるものを取り込みました。また、Visual Studio 2019への対応もしました。
しかし、これだけではやりたいことができていません。
このライブラリーは、.NET Framework 4.5以降のWPFアプリを対象しています。しかし、.NET Core 3.1以降のWPFアプリをサポートできていません。
この.NET Core 3.1以降のWPFアプリをサポートすることが、MetroRadiance
の派生のMetroRadiance.Fork
を作成したモチベーションでもあります。
しかし、いきなり大変更を加えるのは負担が大きいため、まずは、2.5.0として、.NET Framework 4.5以降のみを対象とした修正版をリリースしました。
そこで、今回、本来やりたかったことである
- .NET Core 3.1対応
をします。
実は、本家のMetroRadiance
のIssuesに以下のものが登録されています。
Issues - Add dotNET 4.8 Support - Doesn't work with .net core 3.1
前者は、MetroRadiance.Fork
2.5.0で対応しました。今回、後者の.NET Core 3.1のIssueに対応することになります。
なお、.NET Core 3.1に対応するといっても、.NET Framework 4.5の対応をやめるわけではありません。.NET Core 3.1/.NET Framework 4.5の両対応のライブラリーへと作り変えます。
.NET Core 3.1対応
さて、.NET Frameworkのライブラリーを.NET Core 3.1に対応するにはどうしたらよいのでしょうか?
大まかには以下の作業を行います。
- プロジェクトファイルのスタイルをSDKスタイルに変更
- 複数のターゲットフレームワークの設定
- 参照するライブラリー群を変更
- ライブラリーの変更に伴うコード変更
AssemblyInfo.cs
ファイルの修正
この投稿では、上記について大まかな手順について説明します。詳細は、マイクロソフトのサイト(.NET Core への WPF アプリの移行)に記載されていますので参照してください。
プロジェクトファイルのスタイルをSDKスタイルに変更
.NET Coreを利用するには、プロジェクトファイル(csproj
ファイル)のスタイルを変更する必要があります。.NET CoreのWPFアプリが利用できるプロジェクトファイルはSDKスタイルのみです。従来からの.NET FrameworkのWPFアプリが利用するプロジェクトスタイルとは異なります。そのため、プロジェクトファイルを旧形式からSDKスタイルに変更します。なお、SDKスタイルのプロジェクトファイルは、.NET Coreと.NET Frameworkの両方のWPFアプリをで利用できます。
旧形式のプロジェクトファイル
旧形式では、下記のように<Project ToolsVersion="15.0"
というエレメントから設定が始まります。
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<TargetFrameworkVersion>v4.5.0</TargetFrameworkVersion>
<OutputType>library</OutputType>
<RootNamespace>MetroRadiance</RootNamespace>
SDKスタイルのプロジェクトファイル
SDKスタイルでは、下記のように<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop"
というエレメントから設定が始まります。
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<PropertyGroup>
<TargetFrameworks>net45;netcoreapp3.1</TargetFrameworks>
<UseWPF>true</UseWPF>
<RootNamespace>MetroRadiance</RootNamespace>
Project
要素にSdk
属性を設定することにより、SDKスタイルのプロジェクトファイルになります。
SDKスタイルのプロジェクトファイルはシンプル
SDKスタイルのプロジェクトでは、デフォルト値から変更のある項目のみ記載する形式となります。デフォルト値のままでよいものは記載しません。また、ビルド対象のファイルの設定にはデフォルト値として**/*.cs
が指定されています。そのため、プロジェクトファイルがあるフォルダー以下の.csファイルは自動的にビルド対象となります。
旧形式のプロジェクトファイルでは、ビルド対象のファイルは、すべてプロジェクトファイル内に列挙していました。SDKスタイルのプロジェクトファイルでは、ビルド対象のファイルはほとんど設定する必要はありません。そのため、SDKスタイルのプロジェクトファイルは設定内容が簡素化されます。
実際にMetroRadiance.Fork.Core
のプロジェクトファイルの行数は、変更前の2.5.0では95行だったのが、変更後の3.0.0-alpha00では24行になりました。
複数のターゲットフレームワークの設定
.NET Framework 4.5/.NET Core 3.1の両対応にするためには、ターゲットフレームワークに複数のフレームワークを設定する必要があります。この設定は、ビルド対象のフレームワークの設定にとどまらず、nugetライブラリーの作成の設定にも反映されます。この設定をすることにより、.NET Framework/.NET Coreの両対応のnugetパッケージも作成できます。
SDKスタイルのプロジェクトでは以下のようにターゲットフレームワークを設定します。
一つのターゲットフレームワーク
一つのターゲットフレームワークを指定するときは、<TargetFramework>netcoreapp3.1</TargetFramework>
のようにTargetFramework
(単数形)タグを使います。通常、プロジェクトを作成したときは、TargetFramework
のタグを使ったプロジェクトファイルが作成されます。この設定は、Visual Studio 2019のGUIでも変更できます。
複数のターゲットフレームワーク
一つのプロジェクトファイルで複数のターゲットフレームワークを扱うときは、<TargetFrameworks>net45;netcoreapp3.1</TargetFrameworks>
のように、TargetFrameworks
(複数形)タグを使います。ターゲットフレームワークをセミコロン(;
)で区切り複数の設定ができます。ターゲットフレームワークとして設定する値は、マイクロソフトのサイト(英語/日本語)に情報があります。なお、現在のVisual Studio 2019では、複数のターゲットフレームワークを設定するGUIは設けられていません。変更するにはcsproj
ファイルをXMLエディターで直接編集します。
参照するライブラリー群を変更
プロジェクトファイルの形式をSDKスタイルに変更が完了したら、参照しているプロジェクトおよびライブラリーを再設定します。
プロジェクト参照に関しては、以前と同じように設定します。
ライブラリー参照に関しては、参照するライブラリー群を.NET Core 3.1に対応しているものに変更します。外部ライブラリーの多くは、最新版では.NET Core/.NET Frameworkの両対応になっているので、最新版を参照するのみで完了します。.NET Frameworkのデフォルトのライブラリーを参照していた場合は注意が必要です。.NET Frameworkでは、フレームワークのデフォルトライブラリーとして使えたものが、.NET Coreでは、フレームワークのデフォルトライブラリーとして含まれていないことがあるからです。
MetroRadiance.Fork
では、System.ServiceModel.dllの参照が該当しました。System.ServiceModel.dll内のSystem.UriTemplate
クラスが、.NET Core側にはありませんでした。最終的に.NET Core 3.1側は、nugetパッケージのTavis.UriTemplates
を参照しました。ただし、実装されているメソッドが異なり、実装を変更する必要がありました。.NET Framework側もTavis.UriTemplates
に変更することはできますが、参照モジュールが多くなってしまうため、.NET Framework側は、System.ServiceModel.dllへの参照としました。
参照内容を、.NET Core側と.NET Framework側で分けたい場合は、条件を付けることにより可能となります。具体的には、以下のようにします。
<ItemGroup>
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.0.30" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1' ">
<PackageReference Include="Tavis.UriTemplates" Version="1.1.1" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net45' ">
<Reference Include="System.ServiceModel" />
</ItemGroup>
一つ目のItemGroup
は条件(Condition
)がない参照設定となります。そのため、.NET Core 3.1と.NET Framework 4.5の両方に対して有効となります。
二つ目のItemGroup
は、netcoreapp3.1
に対する条件が付いています。そのため、.NET Core 3.1のみに対する参照設定となります。
二つ目のItemGroup
は、net45
に対する条件が付いています。そのため、.NET Framework 4.5のみに対する参照設定となります。
ライブラリー変更に伴うコード変更
ライブラリー変更に伴うコード修正が必要な場合は、ビルドエラー、もしくは、新たなビルド警告になることがほとんどです。そのため、ビルドエラー・警告を中心にコードを修正します。
ただ、ライブラリー変更に伴うコード変更が必要ないこともあります。
実際、MetroRadiance.Fork.Core
およびMetroRadiance.Fork.Chrome
に対しては、コード変更は必要ありませんでした。変更が必要となったのは、MetroRadiance.Fork
のみです。
これは、先ほど述べた、利用しているクラスがSystem.UriTemplate
クラスからTavis.UriTemplates
のクラスに変更になったことに対する修正です。Tavis.UriTemplates
パッケージを利用することになった.NET Core 3.1側のコードのみを修正しました。
コード内では、プラットフォームに応じた自動的に設定される定義値を使って、コードを分けています。例えば以下のようにコードを分けます。
#if NETCOREAPP
return _templateTable.Match(uri)?.Key == "theme";
#else
return themeTemplate.Match(templateBaseUri, uri) != null;
#endif
.NET Core系の場合は、前者の行のソースコードが有効になり、それ以外(結果として.NET Framework系)では後者の行のソースコードが有効になります。
自動的に定義される定義値は、マイクロソフトのサイト(マルチターゲットを設定する方法)に情報があります。抜粋すると以下の定義値が利用できます。
ターゲット フレームワーク | Symbols |
---|---|
.NET Framework | NETFRAMEWORK , NET20 , NET35 , NET40 , NET45 , NET451 , NET452 , NET46 , NET461 , NET462 , NET47 , NET471 , NET472 , NET48 |
.NET Standard | NETSTANDARD 、NETSTANDARD1_0 、NETSTANDARD1_1 、NETSTANDARD1_2 、NETSTANDARD1_3 、NETSTANDARD1_4 、NETSTANDARD1_5 、NETSTANDARD1_6 、NETSTANDARD2_0 、NETSTANDARD2_1 |
.NET Core | NETCOREAPP , NETCOREAPP1_0 , NETCOREAPP1_1 , NETCOREAPP2_0 , NETCOREAPP2_1 , NETCOREAPP2_2 , NETCOREAPP3_0 , NETCOREAPP3_1 |
#if
ディレクティブで使用できるターゲットフレームワークに関するプリプロセッサ シンボル(2020年7月時点)このように必要となったコード修正を終わらせます。
AssemblyInfo.cs
ファイルの修正
最後に、AssemblyInfo.cs
ファイルを修正します。
SDKスタイルのプロジェクトファイルに形式変更するとAssemblyInfo.cs
ファイルに定義していた情報の多くが、プロジェクトファイル側に設定できるからです。
デフォルト設定(GenerateAssemblyInfo
がtrue)のままでは、同じ情報が両方に定義されていると、二重定義のエラーとなります。そのため、AssemblyInfo.cs
ファイルに定義していた多くの情報をプロジェクトファイル側に移し、AssemblyInfo.cs
ファイルからは削除します。プロジェクトファイル側に移した情報は、ビルドされたモジュールのバージョン情報とnugetパッケージのメタ情報の両方に使われます。
もし、AssemblyInfo.cs
ファイルを変更することなくそのまま使いたい場合は、プロジェクトファイルに<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
の設定を追加します。この場合、プロジェクトファイルに設定したバージョン情報などは、ビルドされるモジュールファイルには自動的に反映されません。プロジェクトファイル側の設定を変更したときは、AssemblyInfo.cs
ファイルの情報も忘れず変更するようにします。
プロジェクトの変換完了
以上が.NET Framework アプリのプロジェクトを.NET Core/.NET Framework両用アプリのプロジェクトに修正する方法の概要です。
モジュール署名とパッケージ署名
近年、一般公開するアプリ、モジュール、インストーラーなどに電子署名をすることは当たり前になってきています。
電子署名の主な役割は二つあります。
- 発行者を明確にする
- モジュールが破損していないことを保証する
この二つの役割は、モジュールを使用する側で電子署名を確認することにより、検証できます。電子署名が無効であれば、不正な証明書を使って署名されたもの(発行者が不明)か、もしくは、電子署名時からファイルが変更されている(壊れている)ときです。
電子署名が有効であれば、電子署名に使われた証明書を確認することにより、発行者が確認できます。また、電子署名をしたときからファイルが変更されていないことも保証されます。
MetroRadiance.Forkのモジュールとパッケージへの電子署名
今回、プロジェクト構成を大きく見直すにあたって、モジュール署名とパッケージ署名をすることにしました。なお、モジュール署名はMetroRadiance.Fork
2.5.0でもしていました。
MetroRadiance.Fork
では、.NETアセンブリの仕組みの一つである、厳密な名前のアセンブリとするためのアセンブリ署名はしません。対応するのは、あくまで、モジュール署名とパッケージ署名のみです。
電子署名をするタイミングは以下の通りにしたいです。
- 通常のビルド時には電子署名はしない
通常のビルド時に電子署名をしてしまうと、ソースコードをクローンした人が、電子証明を持たないためビルドに失敗することになります。これを避けるため、通常のビルド時には電子署名はしません。 - nugetパッケージ作成時にのみ電子署名をする
ソースコードをクローンした人でもnugetパッケージ作成までするひとは多くないため問題なしと判断しました。
SDKスタイルのプロジェクトファイルに変更すると、ビルドメニューのパッケージ(pack)コマンドから、nugetパッケージを作成できます。しかし、このコマンドを使用すると、パッケージ作成ときのみにモジュール署名がしたいのですが、それができないように思われました。
そのため、署名付きパッケージ作成専用のスクリプトで対応することにしました。この結果、ビルドメニューのパッケージコマンドには影響することがなくなり、ソースコードをクローンした人も署名なしのパッケージは作成できることになります。
署名付きパッケージ作成スクリプト
モジュールおよびパッケージ署名付きnugetパッケージを作成するには以下の手順で行います。
- プロジェクトをRelease版でビルドする
- モジュール署名をする
- nugetパッケージを作成する
- nugetパッケージにパッケージ署名をする
dotnet.exe
/ signtool.exe
/ nuget.exe
などを利用して実現します。
以下に実現方法の例を示しますが、これが唯一の方法ではなく、この他にもいろいろな実現方法が存在します。
プロジェクトをRelease版でビルドする
各プロジェクトをRelease版でビルドします。各プロジェクトをもれなくビルドするために、プロジェクトの依存関係を利用せず、各々のプロジェクトをビルドします。
プロジェクトのビルドには、dotnet.exe
のbuild
コマンドを使用します。dotnet.exe
は、Microsoft .NET Core SDK(または、Microsoft .NET SDK)に同梱されています。Microsoft .NET Core SDK(または、Microsoft .NET SDK)がインストールされている環境のPCであれば、”C:\Program Files\dotnet\”のサブフォルダー内に見つかると思います。
dotnet.exe build -c Release --no-incremental --no-dependencies MetroRadiance.Core\MetroRadiance.Core.csproj
のように、プロジェクトファイルを指定してビルドのみします。
モジュール署名をする
モジュール署名はsigntool.exe
のsign
コマンドを使用します。signtool.exe
は、Windows SDKに同梱されています。Windows SDKがインストールされている環境のPCであれば、”C:\Program Files (x86)\Windows Kits\”のサブフォルダー内に見つかると思います。
signtool.exe sign /a /fd sha256 /n "YourCertSubjectName" /d "SignDesc" /tr "http://timestamp.digicert.com?alg=sha256" moduleFiles
のように、モジュールに署名します。
YourCertSubjectName
には、署名に使用する証明書のCommonNameを指定します。MetroRadiance.Fork
では、”nishy software”を指定しています。
SignDesc
には、何のための署名なのかわかる名称を指定します。製品名を指定することが多いと思います。世の中の署名をしているモジュールでもSignDesc
を設定していないことが多いです。ユーザーに分かりやすくするものであるので忘れず設定しましょう。MetroRadiance.Fork
では、MetroRadiance
を指定しています。ただし、今後、MetroRadiance.Fork
に変更する予定です。
nugetパッケージを作成する
モジュール署名が終わったら、各プロジェクトのnugetパッケージを作成します。パッケージ時にプロジェクトのビルドをすることもできますが、署名したモジュールが上書きされてしまうため、ビルドはしません。nugetパッケージの作成のみをします。
パッケージ作成には、dotnet.exe
のpack
コマンドを使用します。dotnet.exe
は、Microsoft .NET Core SDK(または、Microsoft .NET SDK)に同梱されています。Microsoft .NET Core SDK(または、Microsoft .NET SDK)がインストールされている環境のPCであれば、”C:\Program Files\dotnet\”のサブフォルダー内に見つかると思います。
dotnet.exe pack -c Release --no-build -o "%WORK_FOLDER%" MetroRadiance.Core\MetroRadiance.Core.csproj
のように、プロジェクトファイルを指定してパッケージ作成のみします。%WORK_FOLDER%
は、作成したパッケージの出力先フォルダーを指定します。
nugetパッケージにパッケージ署名をする
nugetパッケージの作成が終わったら、各プロジェクトのパッケージにパッケージ署名をします。
パッケージ署名は、nuget.exe
のsign
コマンドを使用します。nuget.exe
は、nuget.orgのサイトからダウンロードできます。
nuget.exe sign "%PACKAGE_FILE%" -Verbosity quiet -CertificateSubjectName "%CERT_SUBJECT_NAME%" -Timestamper "http://timestamp.digicert.com?alg=sha256"
のように、パッケージに署名します。%PACKAGE_FILE%
は、署名するパッケージを指定します。
MetroRadiance.Fork
用に用意した署名付きパッケージ作成スクリプト
前述したコマンドをコマンドラインで入力すれば署名付きパッケージは作成できます。しかし、毎回、同じコマンドを入力するのは大変なので、MetroRadiance.Fork
用のスクリプトを作成しました。スクリプトはgithubで公開しているレポジトリのpackageフォルダーに存在します。
- BuildPackages.cmd
- SignPackages.cmd
の二つのスクリプトがあります。
BuildPackages.cmd
は、前述した一連の作業を行い署名付きパッケージを作成します。このスクリプトを実行するときは、事前に環境変数SIGNTOOL
に、モジュール署名用のスクリプトを設定しておく必要があります。
SET SIGNTOOL=SignModuleFiles.bat
などのように、署名用のスクリプトのパスを環境変数SIGNTOOL
に設定します。SignTool.exe
を使用する場合、SignModuleFiles.bat
の内容は、一番シンプルな状態では、以下のようになります。
SignTool.exe sign /a /fd sha256 /n "YourCertSubjectName" /d "SignDesc" /tr "http://timestamp.digicert.com?alg=sha256" %*
SignPackages.cmd
は、BuildPackages.cmd
の最後の過程(パッケージに署名する)が、タイムアウトで失敗することがあり、この工程のみを再度実行できるスクリプトとなります。
MetroRadiance.Fork 3.0.0-alpha00
というわけで、MetroRadiance.Fork
ライブラリーの.NET Core 3.1対応版をnugetに公開しました。バージョンは、3.0.0-alpha00
としました。ソースコードもgithubで公開しています。
この更新版では、.NET Coreに対応をしたバージョンであることがすぐにわかるように、メジャーバージョンを上げています。しかし、2.5.0から名前空間・クラス名・メソッド名・アセンブリ名などは変更していません。
今までのMetroRadiance
/MetroRadiance.Core
ライブラリーを使っていた.NET Framework WPFアプリの場合は、ソースコードの修正は必要ありません。ライブラリーを置き換えるのみで使えます。
また、本バージョンの主目的である.NET Core 3.1のWPFアプリでも問題なくパッケージ参照できます。
今回の投稿は、nugetに公開した.NET Core対応版のMetroRadiance.Fork
の紹介でした。
“WPFアプリでライトテーマ・ダークテーマに対応するライブラリー#2 .NET Core 3.1対応” への2件の返信