Problem
GUIアプリケーションとして通常のフローで立ち上げることができる。
しかしターミナルなどから引数を与えて立ち上げたときはそのまま標準出力に結果を返して終了する。
そういうことをmacOSアプリでやりたくなったときに参考にしてください。
方法論としてXamarin特有というわけではありませんが,ここではXamarin.Macを使って解説します。
TL;DR
エントリポイント Main.cs
で分岐します。
Prerequisites
- Visual Studio for Mac Version 7.3 (build 799)
- Xamarin.Mac 4.0.0.214
ここで作ったサンプルは GitHub:xamarin-mac-console-sample に公開してあります。
Solution
プロジェクトを作る
Xamarin.Macプロジェクトを適当に作ります。詳しい手順は割愛しますが,ウィンドウが立ち上がるようにしておいてください。
Main.csで引数をパースする
Main.cs
はたいてい次のようになっているはずです。
using AppKit;
namespace ConsoleSample
{
static class MainClass
{
static void Main(string[] args)
{
NSApplication.Init();
NSApplication.Main(args);
}
}
}
NSApplication.Init
を呼ぶと,NSNotificationCenter.DefaultCenter
やら,NSFileManager.DefaultManager
などNS系の静的インスタンスが確保され使えるようになります。つまり呼ばないとNS系のなにがしかは使えたり使えなかったりします。だいたい使えません。
NSApplication.Main
を呼ぶとアプリケーションの起動シーケンスに入り,AppDelegate
へ制御が移っていきます。その前に引数をパースして処理を分岐するのが本稿の目的とするところです。ということで適当にコマンドラインパーサーをNuGet経由で入れるなり,自前でパースするなりしましょう。ここではdotnet-corefxlabリポジトリで提供されているSystem.CommandLine
を使います。雑ですがおおよそ次のようになるでしょう。
using System;
using System.CommandLine;
using AppKit;
namespace ConsoleSample
{
static class MainClass
{
static void Main(string[] args)
{
var input = string.Empty;
ArgumentSyntax.Parse(args, syntax =>
{
syntax.DefineOption("i|input", ref input, true, "Something interesting.");
syntax.ErrorOnUnexpectedArguments = false;
});
if (string.IsNullOrEmpty(input))
{
NSApplication.Init();
NSApplication.Main(args);
}
else
{
Console.WriteLine($"{input.Length} - {input}");
}
}
}
}
プロジェクトの設定から引数を指定してデバッグする,ターミナルから引数を与えて起動するなどで動作を確認できます。ターミナルから起動したい場合は app/Contents/MacOS/
にエントリポイントがあります。
% ./ConsoleSample.app/Contents/MacOS/ConsoleSample -i Hello
5 - Hello
当然ながら,何も引数を与えなければウィンドウが立ち上がり,通常のアプリとして振る舞います。
Conclusion
Main.cs
で起動前になんでもできることがわかりました。
ネイティブライブラリを読み込んでおいたり集約例外ハンドラを設置したりと,それなりのアプリケーションを作る際にはこの辺でいろいろ介入が必要になってくると思います。ぜひ覚えておいてください。