CubePDF シリーズと PDF のパスワード保護について

先日、CubePDF Utility 0.5.4β のリリースが完了しました(リリースノート)。0.5.0β で大幅な改修 を行った都合で一部の機能が移行しきれておらず、0.5.1β 以降は主にそれらの移行期間と位置付けていました。0.5.4β で後述する閲覧機能以外は一通り移行が完了したのではないかと思います。

さて。CubePDF シリーズに関するお問い合わせの中で「パスワード保護」に関するものを比較的よく目にします。そこで、この記事ではパスワード保護(セキュリティ設定)に関連する内容を 2 点ほど記載します。

CubePDF Utility で PDF ファイルを開けない現象について

0.5.0β 以降、重複して頂いているお問い合わせの一つとして「パスワードが設定されていないはずの PDF ファイルを CubePDF Utility で開くとパスワードを要求されるようになった」と言うものがあります。

CubePDF Utility パスワード入力画面

PDF ファイルには「閲覧用パスワード」と「管理用パスワード」と言う 2 種類のパスワードが存在しますが、この内、PDF ファイルの編集には、管理用パスワードを必要とします*1。PDF ファイルの中には、閲覧時にはパスワードを必要としないが PDF ファイルに対する何らかの編集・操作を制限しているものがあり、こう言った PDF ファイルを編集するには、あらかじめ設定されている管理用パスワードを入力する必要があります。

尚、これに関連して、CubePDF Utility 0.4.1β では編集できていたのに 0.5.x 系にバージョンアップすると編集できなくなったと言う報告もいくつか頂いています。この事例に関しては、問題を再現できる PDF ファイルをご提供頂く等して検証しましたが、現時点では「旧バージョン(0.4.1β 以前)で編集できていた事が不都合と言わざるを得ない」と言う結論となっています。完全な再現条件はまだ不明ですが、閲覧用パスワードは設定されていないが管理用パスワードで保護されている PDF ファイルの一部で旧バージョンが各種セキュリティ設定を検知できず、編集が実行されてしまう場合があったようです*2。これが、CubePDF Utility 0.5.0β の改修で図らずとも正しく検知できるように修正され、「編集できなくなった」と言う形で顕在化しました。

CubePDF Utility は 0.5.0β より編集・操作が制限されている事を検知した場合、PDF ファイルを開く際に 管理用パスワード を要求するように変更しました。この挙動に関しては、0.6.x 系では閲覧機能の強化をテーマとする予定で、その一環として再び閲覧条件を緩める方向で修正する予定です。しかし、前述したように 0.5.x 系で編集できなくなった PDF ファイルに関しては、これまで編集できていた事が不都合の可能性が高いため、それらのファイルが管理用パスワード無しで編集可能になる事は今後ありません。誠に申し訳ありませんが、ご理解をお願いいたします。

CubePDF をパスワード保護目的で利用する際の注意点

先日、変換された PDF の色がおかしくなる現象について と言うお知らせを記載しました。この問題に関しては、次バージョンで CubePDF 側での回避方法を追加する予定ですが、ひとまずは Ghostscript 9.26 にダウングレートする形で回避して下さい(CubePDF 1.0.0 with Ghostscript 9.26 のダウンロード)。

今回の不都合ですが、PDF ファイルを CubePDF プリンタ経由で印刷して再度 PDF ファイルに変換する と言う操作を実行した時に発生しやすいようです。当初「何故、PDF ファイルを CubePDF 経由で再度 PDF に変換すると言う操作が必要なのだろうか?」と疑問に思っていたのですが、利用用途を追っている内に「PDF ファイルをパスワード保護する」と言う目的で CubePDF を利用しているユーザがそれなりに存在する事が分かってきました。

まず前提として、CubePDF は利用用途の制限なく誰でも自由に利用できる事を目標の一つとして公開しています。したがって、最終的な出力ファイルに問題ないのであれば、PDF ファイルをパスワード保護する目的で使用して頂いても、何も問題ありません。

ただ、CubePDF プリンタで印刷すると言う事は、いったん PDF ではない別のデータ形式(印刷データ)に変換する 事を意味します。この過程でファイル内部のデータ構造等も大きく変化し、場合によっては最終的な見た目にも影響が及ぶ事もあります。PDF ファイルをパスワード保護する目的で CubePDF を使用する場合、この点をご留意下さい。

CubePDF Utility メイン画面

パスワード保護を始めとした PDF ファイルへの編集機能については、前述した CubePDF Utility と言うソフトウェアを公開しています。また、PDF の結合・分割機能に関しては、さらに簡易的な CubePDF Page と言うソフトウェアも公開しています。元の PDF ファイルのデータ構造をできるだけ保持すると言う点で見ると、これらのソフトウェアを利用する方が良い場合も多いため、もし良ければご検討お願いします。

*1:閲覧用パスワードはユーザパスワード (User Password) と呼ばれる事も多いです。また、管理用パスワードはオーナーパスワード (Owner Password) と呼ばれる事も多い他、編集用パスワード、権限パスワード等いくつかの呼び方が確認されています。

*2:この条件を満たした PDF ファイルでも、CubePDF Utility の旧バージョンがセキュリティ設定を検知できる場合もあり、完全な再現条件は不明となっています。

CubePDF 正式版とカスタム仮想プリンタ CubeVP の公開

本日、CubePDF 1.0.0 を 最初の正式版 としてリリースしました。この記事では、1.0.0 までの道のりの回顧と、同時リリースした有償によるカスタム仮想プリンタ CubeVP の宣伝、もとい、狙いについて記載します。

CubePDF 1.0.0 のリリースまでの道のり

CubePDF メイン画面

CubePDF は 2010 年 7 月 7 日に初版である 0.9.0 β をリリースして、その歴史が始まりました。そこから既に 9 年の月日が経過しており、1.0.0RC1 から数えても 6 年半位となっています。Web 上で「いつまで RC なんだよ」との評判をちらほら見かける事もあり、私としても「それな」とは思っていまいた。

CubePDF は最初期の頃、仮想プリンタを実現するためのシステムライブラリとして RedMon を利用していましたが、いくつかの課題が浮上した結果、最終的に自前のものに換装と言う選択をしました。この時にリリースされたバージョンが 1.0.0RC1 であり、当初の予定では、しばらく様子を見た後に 1.0.0 正式版とするつもりでした。しかし、様子を見ている内に様々な問題が発覚あるいは新たに出現し、それらの対策を検討している内に機を逸して現在に至っています。

参考までに、RC のバージョン毎の主な課題は下記のような感じでした。この内、フォント埋め込み問題に関しては、残念ながら現在においても根本的な解決方法は見つかっておらず、いったん機能自体を取り下げると言う形になっています。

  • PDF へのフォント非埋め込み設定時の文字化け (RC1 ~ RC5)
  • Microsoft ストアアプリ (UWP) への対応 (RC8 ~ RC11)
  • 内部コードの大幅なリファクタリング (RC12)
  • Ghostscript で発生した脆弱性への対応 (RC13 ~ RC15)
  • 仮想プリンタのインストール処理の改善および汎用化 (RC16 ~ RC19)
CubePDF の開発に対するモチベーション

また、別の理由として、私自身の CubePDF の開発に対するモチベーションが一時期低下していた事も挙げられます。 2015 年に Windows 10 がリリースされましたが、この時に「Microsoft Print To PDF」と言う機能が Windows 10 の標準機能として盛り込まれました。これは PDF に変換する機能を仮想プリンタとして実現しているものだったので、「あれ?これもう CubePDF 要らなくない?」と言うのが率直な感想でした。

モチベーションを取り戻したのは、CubePDF 1.0.0RC12 をリリースした頃となります。一つは、Microsoft Print To PDF を調査していると、どうもプリンタドライバの処理として埋め込み画像に対して JPEG 圧縮を実施しているようで、これを回避するのは困難ではないか?(標準機能に対する優位性があるのではないか?)と言う感触を得たのがきっかけとなりました。また、現実の現象として Windows 10 のリリース後も CubePDF のダウンロード数は減少しておらず、このまま終わらすべきではないと言う意識に変わった事も大きかったと思います。

そのような紆余曲折がありましたが、ここ 2 年位の試行錯誤により、個人的にも「不都合はもうない……なんて事を言えるはずはないが、内部コードも開発当初に比べてかなり整理され、ユニットテストの力を借りる事によってデグレードを引き起こす可能性もかなり低減された」と思える程度には改善されてきたため、今回「正式版」を名乗る事を決断しました。

CI サービス上でのテスト結果の様子

有償版カスタム仮想プリンタ Cube VirtualPrinter (CubeVP) の提供開始

CubePDF と CubeVP の比較図

今回のもう一つの大きな発表は、CubePDF で使用している仮想プリンタ部分のみを有償で提供する Cube VirtualPrinter (CubeVP) です。概要に関しては、CubeVP の Web ページ を参照して頂くとして、ここでは CubeVP の提供開始に至った個人的な思いについて記載します。

キューブ・ソフトでは、提供しているソフトウェアの多くのソースコードcube-soft - GitHub にて公開しています。公開を決めた会社としてのきっかけは、CubePDF が Ghostscript 等のライブラリを利用している事によるものだったのですが、それとは別に 自分の書いたコードが公開できる と言う事が私自身にとって非常に強いモチベーションとして機能しています。しかし、(GPL ではない)ソースコードを公開する 会社として のモチベーションが弱い事は長年気になっていた部分であり、もう少し公開する事の意義を持たせたいとも感じていました。

この問題への打開策を模索していた時に、非常に参考となった記事がありました。

カスタマイズをするのはかなりしんどい。自社ではカスタマイズは受け付けていない。もし機能追加をする場合はオープンソースでの公開が前提となる。そのため「カスタマイズのサポート」を Sora を購入している顧客に対してのみ有償にて提供している。

(中略)

つまり、この製品のビジネスモデルこうだ。

  • カスタマイズを提供せずカスタマイズのテクニカルサポートを提供する
  • カスタマイズはせずオープンソースとして公開する
  • WebRTC SFU Sora を購入してもらっている顧客へのみ提供する

リアルタイムな音声や映像をバイナリ一つで配信できるのはとても便利だ。ただ、それを特定のハードに対応させる場合はカスタマイズが必要になる。さらにもともとリアルタイム配信の世界は難しい。

1から自分たちだけでカスタマイズするよりも、そもそも Momo を開発をしている会社と一緒にやっていったほうがいい。

クローズドソースとオープンソース – V – Medium

時雨堂の @voluntas さんの記事は、自分が開発を続ける、あるいは組織を継続させていく上で参考になるものが多く、よく読ませて頂いています。そして、自社ソフトウェアの クローズドソースとオープンソースの境界 を明確に認識できたきっかけとなったのが上記でした。

CubePDF は、以下の 3 種類の機能に大別されます。

  1. 仮想プリンタを構築するためのシステムライブラリ
  2. 仮想プリンタから渡されたデータを PDF 等に変換するためのアプリケーション
  3. これらの機能を連携させ、端末にインストールするためのアプリケーション

この内、2. の機能を完全に取り除き、1. と 3. のみを有償にて提供するのが CubeVP となります。そして、2. の部分を ユーザ自身の手によって実装してもらう ために積極的に技術情報を公開していくのが、今回の大きな挑戦です。

CubeVP に関する技術情報は、Cube.Vp.Docs を随時更新していく事により公開を続けます。また、この技術的サポートの一環として、CubePDF 1.0.0 では、CubePDF のメイン処理に当たる部分のみを抽出し NuGet パッケージとして公開する事も開始しました(Cube.Pdf.Converter)。例えば、CubePDF と同等の変換処理を行う最も簡単なサンプルコードは下記となります。

// using Cube.Pdf.Converter;
// using System.Reflection;

static void Main(string[] args)
{
    var settings = new SettingFolder(Assembly.GetExecutingAssembly());
    settings.Load();    // レジストリの設定をロード
    settings.Set(args); // 仮想プリンタからの引数を解析

    using (var facade = new Facade(settings)) facade.Invoke();
}

CubeVP の概要を知るための最初のステップとしては Tutorial - Cube.Vp.Docs - GitHub を参照下さい。また、Cube.Pdf.Converter ライブラリに関しては、Cube.Pdf.Converter - Cube.Vp.Docs - GitHub に概要を記載しています。

仮想プリンタとして CubePDF が知名度を得ていくにつれて、ありがたい事に、これまで数多くのカスタマイズのご依頼を頂いてきました。しかし、それらの中には、金銭的、あるいは時間的な面で「本来であれば、アプリケーション部分は貴社の方で実装してもらった方が絶対良いはずなのだが……」と感じる事が多々あったのも事実です。今回の CubeVP によって、私(弊社)とユーザの皆様、双方にとって良い形に向かえるようになる事を期待して、これからも開発を続けていきたいと思います。

Visual Studio 2019 と新 csproj への移行

2019 年 4 月 2 日、Visual Studio 2019 が正式版としてリリース (GA: General Available) されました。この記事では、 Visual Studio 2019 に関連する内容として、C# の新しいプロジェクト形式 (csproj) への移行について記載します。新 csproj 自体は Visual Studio 2017 の頃から利用可能でしたが、WinForms や WPF などのプロジェクトに対する Visual Studio 側の対応も 2019 で進んだようなので、移行するには良いタイミングではないでしょうか。

新 csproj の基本的な構成

新しい csproj 形式では、システム側が既定値を設ける事により、多くの項目が省略可能となりました。これは、Visual Studio による自動生成・更新を前提にした複雑なものから、手動による更新も想定した簡素なものへの転換と言う感じで、特にバージョン管理などの観点から見ると非常に楽になったように思います。

以下に、新 csproj において省略しない方が良い(多くの場合、初期設定とは異なる)ものを記述します。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net45</TargetFramework>
    <LangVersion>latest</LangVersion>
    <Platforms>AnyCPU;x86;x64</Platforms>
    <Optimize>true</Optimize>
    <EnableDefaultNoneItems>false</EnableDefaultNoneItems>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
    <DefineConstants Condition=" '$(Configuration)' == 'Debug' ">DEBUG;TRACE</DefineConstants>
  </PropertyGroup>
</Project>

TargetFramework には net45, netcoreapp3.0, netstandard2.0 などの文字列を指定します(参考:Target Frameworks Reference for NuGet)。この項目は、旧 csproj の TargetFrameworkVersion から置き換わったものです。また、新 csproj 形式では TargetFrameworks(複数形)を代わりに用いる事で、複数の TargetFramework を ";" (semicolon) で区切って列挙する事もできるようです。

LangVersion には C# のバージョンを指定します(参考:Select the C# language version)。具体的なバージョン番号を指定する事もできますが、多くの場合においては、latest または latestMajor のどちらかだと思います。Visual Studio 2019 においては LangVersion の初期設定は latest なので、この項目は省略しても問題ありません。ただ、AppVeyor 等で継続的インテグレーション (CI: Continuous Integration) を実行する際に意図しない挙動をいくつか確認したため、明記する事にしています。

PlatformsVisual Studio 上で x86 や x64 を選択可能にしたい時に明記します。C#/.NET では多くの場合において AnyCPU で良いと思いますが、Unmanaged なライブラリとの兼ね合いでプラットフォームを固定したい場合などに設定します。

GenerateDocumentationFile は、XML Documentation Comments に従って記述されたコメントに基づいて XML ファイルを生成するかどうかを決定します。尚、XML ファイル自体は後述する DocumentationFile に出力パスを記述すれば、この項目がなくても生成されます。しかし、その場合には生成された XML ファイルが NuGet パッケージ (*.nupkg) に含まれないようです。NuGet パッケージにも含める場合、この項目を true で明記する必要があります。

DefineConstants は必要なシンボルを記述するための項目ですが、省略時には TRACE のみが設定されるようです。多くの場合、Debug モード時には TRACE に加えて DEBUG シンボルの存在も想定されているので、それを明記しています。

既定の Include に関する設定

新 csproj では、ソースファイル等に関する記述をできるだけ省略可能にするために、既定では以下の規則で自動的に Include される事になっています(参考:Default compilation includes in .NET Core projects)。

Element Include Remove
Compile **/*.cs N/A
EmbeddedResource **/*.resx N/A
None **/* **/*.cs; **/*.resx

しかし、これらの Include 規則は邪魔になる事もあるため、無効にする方法もいくつか提供されています。具体的には、上記の全ての規則を無効にするには EnableDefaultItems を、Compile および None の項目のみを無効にするにはそれぞれ EnableDefaultCompileItems, EnableDefaultNoneItems を false で記述します(EnableDefaultEmbeddedResourceItems が存在するかどうかは不明)。

個人的には None の Include 規則には不安に感じる所もあるため、EnableDefaultNoneItems を false に設定した上で自力で明記する事にしています。

NuGet パッケージに関する設定

新 csproj では、従来 AssemblyInfo.cs および *.nuspec に記述されていた内容が全て csproj に統合されました。生成されるアセンブリのメタ情報、および NuGet パッケージに関する項目は下記の通りです。

<PropertyGroup>
  <Version>1.0.0</Version>
  <Authors>clown;cube-soft</Authors>
  <Company>CubeSoft</Company>
  <Description>Some description.</Description>
  <Copyright>Copyright © 2010 CubeSoft, Inc.</Copyright>
  <PackageTags>Ta1;Tag2;Tag3</PackageTags>
  <PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
  <PackageProjectUrl>https://github.com/cube-soft/$(AssemblyName)</PackageProjectUrl>
  <PackageIconUrl>https://github.com/cube-soft/$(AssemblyName)/blob/master/Icon.png?raw=true</PackageIconUrl>

  <!-- プロジェクト名と異なる場合 -->
  <Product>Cube.Custom.Product</Product>
  <AssemblyName>Cube.Custom.Product</AssemblyName>
  <AssemblyTitle>Custom Title</AssemblyTitle>
  <RootNamespace>CustomNamespace</RootNamespace>

  <!-- NuGet パッケージの生成可否を明記する場合 -->
  <IsPackable>true</IsPackable>

  <!-- 厳密な名前付けに関する設定 -->
  <SignAssembly>true</SignAssembly>
  <AssemblyOriginatorKeyFile>Cube.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>

PackageLicenseExpression には、成果物(NuGet パッケージ)に適用するライセンスを指定します。指定可能な文字列に関しては SPDX License List を参照下さい。これまで、ライセンスは URL で指定していましたが、NuGet の仕様変更により deprecated となったようです。

Product, AssemblyName, AssemblyTitle, RootNamespace の 4 種類は、省略時にはプロジェクト名(csproj 拡張子を除く名前)が設定されます。

IsPackable は、プロジェクトが NuGet パッケージを生成可能かどうかを指定します。既定値は true なので明記する必要性は薄いと思いますが、AppVeyor 等の CI サービスにおいて、自動的に NuGet パッケージを生成して欲しくない時などに false を設定する事があります。

SignAssembly および AssemblyOriginatorKeyFile は、厳密な名前付けに関する項目です。これらの項目は旧 csproj と同じなので、詳細は省略します。

AnyCPU に関する設定

新 csproj は Platform が AnyCPU の場合、初期設定では(例えば)"bin/Release/net45" に出力されます(ちなみに、旧 csproj は "bin/Release")。

<PropertyGroup Condition=" '$(Platform)' == 'AnyCPU' ">
  <OutputPath>bin\Any CPU\$(Configuration)\</OutputPath>
  <DocumentationFile>bin\Any CPU\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
</PropertyGroup>

多くの場合においては初期設定のままで問題ないと思いますが、AppVeyor 等の CI サービスにおいて意図しない挙動をいくつか確認したため、AnyCPU の OutputPath を上記のように設定しています。尚、新 csproj では OutputPath で設定された文字列に $(TargetFramework) を追加したものが実際の出力パスとなるようです。

Properties.Resources に関する設定

前述した通り、新 csproj において *.resx ファイルは EmbeddedResource として自動的に Include されます。しかし、この状態では Visual Studio 上でのリソース編集が Resources.Designer.cs に反映されません。これを反映させるには、旧 csproj で指定されていた内容を明記する必要があります。

<ItemGroup>
  <Compile Update="Properties\Resources.Designer.cs" DependentUpon="Resources.resx" AutoGen="True" DesignTime="True" />
  <EmbeddedResource Update="Properties\Resources.resx" Generator="ResXFileCodeGenerator" LastGenOutput="Resources.Designer.cs" />
</ItemGroup>

ここで重要なのは、Update 属性を使用している点です。*.resx は全て追加済みとなっているため、Include 属性で明記すると重複エラーとなります。これを回避するには、一つは前述した EnableDefaultItems に false を設定する事、そしてもう一つが Update 属性を使用する事です。

尚、パスの区切り文字を "/" (slash) にした場合、Visual Studio 上でリソースを編集したタイミングで csproj が自動的に編集されてしまいました("Properties/Resources" と "Properties\Resources" が両方とも記述される)。少なくとも .NET Framework をターゲットにしている場合、もうしばらくは "\" (backslash) を使用するほうが無難なようです。

参照に関する設定

新 csproj では、参照に関する記述方法も簡素化されています。特に、これまで packages.config と旧 csproj の 2 種類のファイルに記述されていた NuGet パッケージの参照内容が、新 csproj では PackageReference と言う項目に統合されました。

<ItemGroup>
  <ProjectReference Include="..\Another\Another.csproj" />
  <PackageReference Include="SomePackage" Version="1.0.0" />
  <Reference Include="System.For.Bar" />
</ItemGroup>

ProjectReference や PackageReference は推移的な参照をサポートしており、ProjectReference の ProjectReference や PackageReference の PackageReference などは自動的に解決してくれます。このため、新 csproj には直近のプロジェクトまたは NuGet パッケージの参照のみを追加すれば OK です。ただし、推移的なプロジェクト参照に関しては、全てのプロジェクトが新 csproj 形式でなければならないそうです(参考:新しい csproj 形式)。

削除するファイル一覧

前述したように、新しい csproj 形式では、これまでいくつかのファイルに分割して記述されていた情報が一つのファイルに統合されました。そのため、以下のファイルは必要な情報を csproj に記述した後に削除する必要があります。

  • **/Properties/AssemblyInfo.cs
  • **/packages.config
  • **/*.nuspec

WinForms

ここからは、WinForms および WPF アプリケーションの新 csproj について説明します。

基本的な構成

WinForms の基本的な設定は下記の通りです。尚、これまで記述した内容の多くを省略していますが、それらの設定も必要に応じて追加して下さい。

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net45</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
    <EnableDefaultNoneItems>false</EnableDefaultNoneItems>
    <ApplicationIcon>App.ico</ApplicationIcon>
    <ApplicationManifest>App.manifest</ApplicationManifest>
  </PropertyGroup>
</Project>

重要な点は、Project の Sdk 属性を Microsoft.NET.Sdk.WindowsDesktop にする事と、UseWindowsForms を true で明記する事です(参考:Windows Desktop)。これらの項目の意味については、後述します。

OutputType の規定値は Library のようです。そのため、WinForms や WPF アプリケーションの場合、この項目を WinExe に設定します。

ApplicationIcon および ApplicationManifest には、アイコン等のファイルを指定します。これらの項目は旧 csproj と同じなので、詳細は省略します。

Microsoft.NET.Sdk.WindowsDesktop に関して

Visual Studio 2019 かつ .NET Core 3.0 の環境において新たに指定可能となった Microsoft.NET.Sdk.WindowsDesktop および UseWindowsForms ですが、手元の環境で確認した限り、.NET Framework では既定の参照内容に影響を及ぼすようです*1。以下の図は、UseWindowsForms の設定値と Visual Studio 2019 上で確認できる参照内容の対応関係を表したものになります。

UseWindowsForms の設定による違い

この図を見ると、UseWindowsForms が true の場合、WinForms ライブラリである System.Windows.Forms が参照に追加されている事が分かります。他に UseWPF と言う項目も存在しますが、true にした場合には同様に PresentationCore 等の WPF に必要なライブラリが参照に追加されます。ただし、これらの項目は SdkMicrosoft.NET.Sdk.WindowsDesktop の場合のみ有効で、Microsoft.NET.Sdk の場合は無視されます。

注意点として、Visual Studio 2017 では、.NET Core 3.0 SDK の有効・無効に関わらず Microsoft.NET.Sdk.WindowsDesktop を指定したプロジェクトの読み込みに失敗します。また、Visual Studio 2019 であっても .NET Core 3.0 が無効の場合、ビルドに失敗します。したがって、これらの環境との互換性を考慮する場合、SdkMicrosoft.NET.Sdk を指定した上で各種ライブラリの参照を明記する必要があります。

Include および Update 規則

WinForms におけるソースファイル等の Include および Update 規則に関する記述内容は下記になります。

<ItemGroup>
  <Compile Update="Views\**\*.cs" SubType="Form" />
  <Compile Update="Views\**\*.Designer.cs" SubType="Code" />
  <EmbeddedResource Update="Views\**\*.resx" DependentUpon="%(Filename).cs" />
  <None Include="Assets\**\*" />
  <None Include="App.config" />
</ItemGroup>

この中で最低限必要な項目は EmbeddedResource です。前述したように *.resx は自動的に Include されますが、これだけだと Form にアイコンを設定している場合などで実行時エラーが発生します。これを防止するために WinForms に関連する *.resx に対して、DependentUpon 属性を指定する形で Update します。尚、Include や Update にワイルドカードで指定した場合、"%(Filename)" で各ファイルの拡張子を除いた名前を取得できるようです。

2 種類の Compile 設定は、各種 Form を Visual Studio のデザイナ上で編集可能にするための設定です。ただし、これらの設定は Visual Studio 2017 では効果がありませんでした。

WPF

次は、WPF アプリケーションの新 csproj について説明します。

基本的な構成

WPF の基本的な設定は下記の通りです。UseWindowsForms が UseWPF に置き換わったのみで、それ以外は全て WinForms と同様です。

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net45</TargetFramework>
    <LangVersion>latest</LangVersion>
    <UseWPF>true</UseWPF>
    <EnableDefaultNoneItems>false</EnableDefaultNoneItems>
    <ApplicationIcon>App.ico</ApplicationIcon>
    <ApplicationManifest>App.manifest</ApplicationManifest>
  </PropertyGroup>
</Project>

尚、前述したように UseWindowsForms および UseWPF は各ライブラリの参照設定に関するもののようなので、併記する事も可能です。WPF の場合、GUI コンポーネントが足りない等の理由で WinForms を併用する事もあるので、その場合には UseWindowsForms も true で設定します。

Include および Update 規則

WPF におけるソースファイル等の Include および Update 規則に関する記述内容は下記になります。

<ItemGroup>
  <ApplicationDefinition Include="App.xaml" SubType="Designer" Generator="MSBuild:Compile" />
  <Page Include="Views\**\*.xaml" SubType="Designer" Generator="MSBuild:Compile" />
  <Compile Update="Views\**\*.xaml.cs" SubType="Code" DependentUpon="%(Filename)" />
  <Resource Include="Assets\**\*" />
  <Resource Include="App.ico" />
  <None Include="App.config" />
</ItemGroup>

まず、App.xaml を ApplicationDefinition で明記する必要があります。次に、Page および Compile の項目で、それ以外の *.xaml ファイル(およびコードビハインドに当たる *.cs ファイル)を追加します。この時、App.xaml が同じディレクトリに存在すると重複エラーとなります。その場合は、Page の項目に対して Exclude="App.xaml" と言う記述を追加して下さい。個人的には、ディレクトリ階層で区分する事で解決するようにしています。

新 csproj 具体例へのリンク一覧

最後に、これまでに説明した内容を実際のプロジェクトで記述したものをサンプルとしていくつか紹介します。

*1:尚、TargetFramework を netcoreapp3.0 に設定した場合、現時点では Microsoft.NET.Sdk.WindowsDesktop と記述した時点で UseWindowsForms, UseWPF の設定に関わらず全ての WinForms/WPF ライブラリが参照に追加されるようです。