PG日誌

各記事はブラウザの横幅を1410px以上にすると2カラムの見出しが表示されます。なるべく横に広げてみてください。

.NET Coreでクリップボードを利用した単一実行可能ファイルを作成する

.NET Core でプログラムを(Windows上で)実行するとGUIDが100件ぶんクリップボードに設定された状態にするコンソールアプリを作成したいと思います。その際に単一実行可能ファイル形式でバイナリを出力する方法と合わせて紹介したいと思います。

今回の記事の目的は以下の通りです。

  • (1) .NET Core の WindowsForms の機能をコンソールアプリからも利用したい
  • (2) 生成物は Exe ひとつにまとめたい

.NET Core は DLL をコマンドラインから dotnet run して実行しますが、exe が同時に生成されこれが dotnet run のショートカットになっているので生成物がDLL + Exe + 定義ファイルと増えてしまい配布が面倒です。どうせ Windows 上でしか実行しないプログラムなら exe いっこでいいじゃん。昔みたいに。というのが動機です。

プログラムで処理する内容は実際は何でも良いので .NET Core 時代の Windows 専用なアプリ作成パターンとして使いまわせるような形を紹介したいと思います。

実装・確認環境

この記事は以下の環境で動作確認を行っています。

  • Visual Studoo 2019(16.7.1)
  • .NET Core 3.1 + C# 8.0
  • Windows10 1904(19041.450)

作成方法

さっそく作成方法を紹介していきます。

(1) プロジェクトの作成

以下でプロジェクトを新規作成します。コンソール アプリ(.NET Core)を選択しました。

.NET Core のコンソールアプリのテンプレートを選択します。
.NET Core のコンソールアプリを選択

(2) Windows Formsの機能が呼び出せるように設定を変更

今回クリップボードの機能を使いたいのでClipbord クラスを使用します。ただしこのクラス、System.Windows.Forms 名前空間に入っているWindows 専用のクラスになるのでデフォルトではプロジェクトで使用できません。そこで System.Windows.Forms 名前空間を使用できるようにプロジェクトの設定を変更します。

以前の記事でも利用可能にする方法を書いています。詳細はこちらを参照してほしいですがここでも簡単に説明します。

takap-tech.com

ソリューションエクスプローラーのプロジェクトを右クリックして「プロジェクト ファイルの編集」を選択します。初期状態では以下のようになっているはず。

// .csproj ファイル
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

</Project>

これを以下の通り書き換えます。

// .csproj ファイル
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop"> <!-- ★★★(1) -->

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms> <!-- ★★★(2) -->
  </PropertyGroup>

</Project>

そうするとプロジェクトが Windows 専用の状態に変更され以下参照がプロジェクトに追加さたことがソリューションエクスプローラ上から確認できると思います。公式のリファレンスは【MSDN】Windows フォーム デスクトップ アプリを .NET Core に移植する方法 です(すぐリンクが切れるのでなければ文字列で検索してください)

依存関係 > フレームワーク > Microsoft.WindowsDesktop.App.WindowsForms

(3)実装

クリップボードに100件分のGUIを作成して貼り付けるプログラムを Program.cs に書きます。

// Program.cs
using System;
using System.Text;
using System.Windows.Forms; 

namespace ConsoleApp1
{
    internal class AppMain
    {
        [STAThread]
        public static void Main(string[] args)
        {
            var sb = new StringBuilder();
            for (int i = 0; i < 100; i++)
            {
                sb.AppendLine(Guid.NewGuid().ToString());
            }

            Clipboard.SetDataObject(sb.ToString(), true);
        }
    }
}

簡単ですが、こんな感じで大丈夫です。

(4)単一実行可能なファイルバイナリを作成する

(.NET Core 3.0以降限定ですが)生成物が単一実行可能ファイル(=Exeだけ, 正式名称は"Single-file executables")な形式になるようにプロジェクトに設定を行います。

ソリューション エクスプローラー上のプロジェクトのを右クリックして「発行」を選択します。発行先はフォルダーを指定して完了を押下します。

ソリューションエクスプローラー上のプロジェクトのメニューの「発行」を選択します。
プロジェクトの「発行」の選択

初回だけ以下の様にダイアログが表示されます。ここは「フォルダー」をリストから選んで「次へ」を選択します。

発行を初回選択するとどこに発行するか聞かれるので「フォルダー」をリストから選択します。
「発行」の初回選択自のダイアログ

次のダイアログの画面は適当(あとから変更できるので)で「完了」を選択します。

「フォルダー」を選んで「次へ」を選択したときに表示されるダイアログの画面です。
ダイアログの次の画面

操作が完了すると、プロジェクトに以下のようなフォルダとファイルが作成されるので確認だけしておきます。

ConsoleApp1
  + Properties
    + PublishProfiles
      + FolderProfile.pubxml

このページでは少し古いバージョンのため、pubxml を直接編集していますが、最新の VisualStudio のバージョンではこれらの指定は全て GUI から設定できるようになったので中を直接書き換えたりしないようにしてください。(トラブルの元ですので、、、)

「発行」の画面に表示されている、ターゲットランタイムの右側のペンのアイコンをクリックし、「プロファイル設定」ダイアログで「ターゲットランタイム」を「win-x64」にすると「ファイル公開オプション」がダイアログの下側に表示されるのでそこで「単一ファイルの作成」のチェックを入れて「保存」します。

発行画面から「編集」ボタンを押します。
発行画面から「編集」ボタンを押す

表示されるダイアログを赤枠のように指定します。以下の画像の赤枠のように選択を変更してください。

発行のプロファイル設定を変更し単一ファイルを生成するように指定します。
発行のプロファイル設定の変更

これで「保存」を選択し画面上の「発行」ボタンを押すと「ターゲットの場所」に exe と pdb ファイルの2つが生成されます(ビルドしただけではそうならないので必ず「発行」してください)

ReadyToRun コンパイルを有効にする

ReadyToRun コンパイルを有効にするを選択した場合
ReadyToRun コンパイルを有効にする

発行画面にあった ReadyToRun コンパイルを有効にするを有効にした場合だいたい以下のような効果があります。

  • アプリをある程度事前コンパイルして発行したバイナリに含める機能
  • 起動時間がかなり早くなる(起動時のJITコンパイルの低減{完全ではないらしい})
  • コンパイル時間はかなり長くなる(初回は10秒~かかる
  • バイナリサイズが大きくなる(今回の例でも最低でも1.5MB程度増加

リファレンスの【MSDN】ReadyToRun イメージに同様の説明があります。MSDNはすぐリンク切れになるのでリンクが切れてた場合自分でGoogle検索してください。

さいごに

上記で説明した作業は、.NET Framework の Windows Form プロジェクトでは必要ありません。正直現状めんどくさいだけかもしれません。

ただ、.NET Framework 4.8 で最後のバージョンとなって .NET Core ベースの .NET 5 で C# が継続していくのでいずれこのような作業が必須になっていくのかと思います。