Location via proxy:   
[Report a bug]   [Manage cookies]                

dullwhaleのメモ帳

何度も同じことを調べなくてよいように...

GNU拡張のFORTRANでシグナルを扱う

FORTRANでキーボード割り込みを受け取ってプログラムを安全に終了したくなった。

GNU拡張のFORTRANPOSIXシグナルを受け取ってプログラムを終了するプログラムを実装する。

前提

# FORTRANコンパイラであるgfortranをインストール
$ sudo apt install gfortran
# インストールされたgfortranのバージョンを確認
$ gfortran -v
Using built-in specs.
COLLECT_GCC=gfortran
COLLECT_LTO_WRAPPER=/usr/lib/gcc/aarch64-linux-gnu/10/lto-wrapper
Target: aarch64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Debian 10.2.1-6' --with-bugurl=file:///usr/share/doc/gcc-10/README.Bugs --enable-languages=c,ada,c++,go,d,fortran,objc,obj-c++,m2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-10 --program-prefix=aarch64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-bootstrap --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-libquadmath --disable-libquadmath-support --enable-plugin --enable-default-pie --with-system-zlib --enable-libphobos-checking=release --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --enable-fix-cortex-a53-843419 --disable-werror --enable-checking=release --build=aarch64-linux-gnu --host=aarch64-linux-gnu --target=aarch64-linux-gnu --with-build-config=bootstrap-lto-lean --enable-link-mutex
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 10.2.1 20210110 (Debian 10.2.1-6)

ソースコードコンパイル

gfortranのマニュアルに記載されているサンプルコードを少しだけ改変して実装した。 ここで使っているsignal()sleep()GNU拡張であり純粋なFORTRANでは利用できないことに注意せよ。

test_signal.f90

program test_signal
  implicit none
  logical :: is_running = .true.
  intrinsic :: signal, sleep
  call signal (2, signal_handler)  ! キーボド割り込み SIGINT
  call signal (15, signal_handler)  ! killシグナル -9を付けていないときのkillコマンド SIGTERM

  do while(is_running)
    print *, "do"
    call sleep (1)
  end do
  print *, "end"

contains
  ! POSIX.1-2017:  void (*func)(int)
  subroutine signal_handler() bind(C)
    print *, 'signal_handler invoked'
    is_running = .false.
  end subroutine

end program test_signal

コンパイル

$ gfortran test_signal.f90
$ ls a.out
a.out

動作テスト

# キーボード割り込み SIGINTのテスト
$ ./a.out
 do
 do
^C signal_handler invoked
 end

# killコマンドでSIGTERMを送るテスト
$ ./a.out > output.txt &
[1] 3800740
$ kill 3800740
$ cat output.txt
 do
 do
 signal_handler invoked
 end

cf. https://gcc.gnu.org/onlinedocs/gfortran/SIGNAL.html

シェルからPythonの単一ファイル中の関数を呼ぶ方法

🙅‍♂️

python -c "import lambda_function; lambda_handler(None, None)"

🙆‍♂️

python -c "import lambda_function; lambda_function.lambda_handler(None, None)"

検索して上位に出てくる方法では上手くいかない。

前提と詳細

ファイル名 lambda_function.pyの中に次のような関数が定義されているとする。 これはシンプルなAWS Lambda関数の実装を想定している。

def lambda_handler(event, context):
    return {
        'isBase64Encoded': False,
        'statusCode': 200,
        'headers': {},
        'body': '{"result":"ok"}',
    }

この関数をシェルから実行するためには次のようにする。

python -c "import lambda_function; lambda_function.lambda_handler(None, None)"

検索して上位に出てくる次の呼び出し方ではエラーになる。

# 注意!ダメな方法
$python -c "import lambda_function; lambda_handler(None, None)"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
NameError: name 'lambda_handler' is not defined

AWS Lambda関数とAPI Gatewayを繋ぐ際apigateway put-integrationで指定するパラメータのメモ

多くのユースケースでは次を指定せよ。

--type AWS_PROXY
--integration-http-method POST

すこしだけ説明

CLIのドキュメントを読むと、--typeとして指定する値にはいくつもの種類があるように見える。 だが、Lambda関数とAPI Gatewayを繋ぐというケースでは基本的に次の2パターンの値だけ気にすれば良い。

AWS_PROXY

「Lambdaプロキシ統合」とも説明されるタイプ。 Lambda関数とAPI Gatewayを繋ぐ際の一番シンプルで、採用されることが多い方法。 統合リクエスト/レスポンスというものを設定する必要がないから楽。

このタイプを指定した場合、--integration-http-methodはPOSTにする必要がある(MUST)。

AWS

「Lambdaカスタム統合」とも説明されるタイプ。 統合リクエスト/レスポンスというものをテンプレートドキュメントで設定し、データのマッピングの設定が必要。

Lambda側はJSONでのデータやり取りを想定しているが、外部のAPIXMLでしかやりとりできないなど、リクエスト/レスポンスの変換処理が必要な場面で使うのだろう。

OpenAPIで記述したAPI仕様をRedocを使ってsingle fileのHTMLにする

いくつも方法があるものの、新しめの手法に中々たどり着けなかったからメモする。 この方法も(開発段階という意味で)previewらしいから変化する可能性が高い。注意が必要。

ライブプレビューする

ライブプレビューにはいくつか方法がある。 ここでは、ファイルをウォッチして自動で再ビルド、サーブしてくれる方法を説明する。 APIの定義があるファイルをapi.yamlとする。

npx @redocly/cli preview-docs api.yaml

デフォルトではlocalhost:8080でプレビューをサーブする。 WSLなどの環境で便利。 ただ、現時点では構文が間違っていると例外を吐いて勝手に終了してしまう問題がある。

single fileのHTMLを生成する

CIや他人との共有に備えて、一つのファイルで完結するHTMLを生成することもできる。

npx @redocly/cli build-docs api.yaml

Windows App SDK(WinUI3)でNotifyIconを持つプログラムを作成する

この記事は、Windows App SDK(WinUI3)でNotifyIconからウィンドウを開く方法について説明する。 NotifyIconの実装にはライブラリH.NotifyIconを用いる。

NotifyIconとその使われ方

Windowsにおいて、普段はデーモンのように動くが時々GUIで対話するアプリケーションを作りたい。 そのようなアプリケーションのGUIを起動する手段として、NotifyIconが使われることがある。 NotifyIconはsystem tray iconやtaskbar iconとも言われ、デフォルトでタスクバーの右に配置される。 次の図にNotifyIconの例を示す。 赤い枠で囲まれた部分に各アプリケーションや機能のNotifyIconが格納されている。

図: Windows 11におけるsystem tray中のNotifyIconの例

アイコンの右クリックメニューでいくつかの簡単な(GUIの用語としての)コマンドを実行でき、複雑な操作に備えてウィンドウを開けるようになっているものが多い。

対象

事前作業 プロジェクトを作成しUnpackagedにする

この節では事前作業としてVisual Studio 2022でソリューション・プロジェクトを作成し、アプリケーションのベース部分を作成する。 配布は行わないから、Unpackagedアプリとして作成する。

詳細は公式ドキュメントに記載されているから、ここでは要点だけ記述する。

1 . プロジェクト作成時のテンプレートでは「C#」、「Windows」、「WinUI」でフィルタし、「空のアプリ、パッケージ (WinUI 3 in Desktop)」を選択する。

2 . *.csprojファイルを開き、<PropertyGroup>の下の階層に以下の記述を追加する。

<WindowsPackageType>None</WindowsPackageType>

3 . Visual Studioからの実行の構成がデフォルトで「${プロジェクト名} (Packaged)」になっているから、「${プロジェクト名} (Unpackaged)」に変更したうえで実行してみる。

4 . ビルド、実行が行われ、起動したウィンドウ中央の「Click Me」ボタンを押して「Clicked」に変化すれば、この節手順は完了。

続く節ではサードパーティーライブラリH.NotifyIconの導入理由と手順を示す。

なぜサードパーティーのライブラリを使うのか

この記事を書いた時点においては、NotifyIconの機能はWindows App SDKで提供されていないようだ。 Win32APIを直接コールするか、Windows FormsのAPIを呼び出す必要がある。 どちらの方法を取っても、記述量が増えバグのリスクも高まる。 特にWin32APIはより低レベルのインターフェイスを提供する分、危険である。

対して、H.NotifyIconはごく少ないコード量でNotifyIconを利用でき、危険なコードになりにくい。 同様の機能を提供し、かつ開発が継続しているライブラリが他に見当たらないからこれを使う。

H.NotifyIconの導入

Windows App SDKと一緒に使うことを想定したNuGetパッケージH.NotifyIcon.WinUIをインストールする。 H.NotifyIconではないことに注意せよ。 さらに、他のGUIフレームワーク向けのよく似たパッケージが複数用意されているから混同に注意せよ。 インストールはメニューの「プロジェクト(P)」>「NuGet パッケージの管理(N)」から行うのが簡単だろう。

NotifyIconをクリックしてウィンドウを開けるようにする

この節では、常駐アプリとして稼働し、必要なときだけNotifyIconをクリックすることでウィンドウを開くアプリケーションを作成する。 記述するコードは少ないものの、コード量に対して考えるべきことが多いから、以降いくつかの節に分けて説明する。

ウィンドウが全て閉じられてもアプリケーションが動き続けるようにする

よくある常駐アプリのように、バックグラウンドで稼働し続け、必要なときだけウィンドウを表示できるよう実装する。 これは実装の視点から見ると、全てのウィンドウが閉じられてもアプリケーションが起動し続ける必要がある。 これを実現するためにClosedイベントをキャッチしてデフォルトのハンドラからイベントを隠ぺいする。 詳しくは次の記事を参照せよ。

Windows App SDK(WinUI3)でウィンドウを閉じられてもアプリケーションを動かし続ける - dullwhaleのメモ帳

ここでは、Closedイベントの隠ぺいに加えてウィンドウを非表示にする処理を加える。 デフォルトで作成されるMainWindowのコンストラクタ内でイベントハンドラを追加する。 MainWindow.xaml.cs

public MainWindow()
{
    // 中略
    // ウィンドウが閉じられたイベントに対するイベントハンドラを追加
    Closed += (sender, args) =>
    {
        // イベントを処理済みとしてマークして、デフォルトのハンドラへの伝播を阻止する。
        // デフォルトのハンドラはアクティブなウィンドウが1つもないとアプリケーションを終了させる。
        args.Handled = true;
        // ウィンドウを閉じる代わりに隠す。
        this.Hide();
    };
}

NotifyIconをXAMLで作成する

H.NotifyIconのREADMEにあるサンプルを流用して、最低限のNotifyIconを実装する。 左クリック時のイベントハンドラを指定している部分だけが元のサンプルとの違いである。

LeftClickCommand="{x:Bind IconClicked}"

MainWindow.xaml

<!-- 中略 -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
    <Button x:Name="myButton" Click="myButton_Click">Click Me</Button>
    <tb:TaskbarIcon LeftClickCommand="{x:Bind IconClicked}">
        <tb:TaskbarIcon.IconSource>
            <tb:GeneratedIconSource
        Text="❤️"
        Foreground="Red"
        />
        </tb:TaskbarIcon.IconSource>
    </tb:TaskbarIcon>
</StackPanel>
<!-- 中略 -->

非表示をウィンドウを表示するための下準備をする

テンプレートのコードを少し修正し、続く節でイベントハンドラからウィンドウを表示できるよう準備をする。 既存のAppクラスの一部を以下のように書き換え、staticメンバを作成して簡易的なシングルトンとして機能させる。

App.xaml.cs

protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
    MainWindow = new MainWindow();
    MainWindow.Activate();
}

public static Window MainWindow;

NotifyIcon左クリック時にウィンドウを表示する

上で指定したイベントハンドラIconClickedを実装する。 このイベントハンドラの指定には癖がある。 H.NotifyIconのコントロールではメソッドを直接指定できず、System.Windows.Input.ICommand型の変数をx:Bindする必要がある。 この原因は恐らく、H.NotifyIconがWPFwindows presentation foundation)をターゲットとして作成され、後からWindows App SDKに対応させたからだと思われる。

まずはinterface ICommandを実装するclassを実装する。 ICommandの設計思想は置いておき、定義されている3つのものを次のように実装すればよい。

MainWindow.xaml.cs

using System.Windows.Input;

// 中略

public class IconClickedCommand : ICommand
{
    // 使わないからnullにしておく
    public event EventHandler CanExecuteChanged = null;
    public bool CanExecute(object parameter)
    {
        // この単純な実装では常にtrueを返せばよい。
        return true;
    }

    // 処理の実体をここに記述する
    public void Execute(object parameter)
    {
        App.MainWindow.Activate();
    }
}

そして、このインスタンスをメンバに登録する。

MainWindow.xaml.cs

public sealed partial class MainWindow : Window
{
    private readonly IconClickedCommand IconClicked;
    public MainWindow()
    {
        this.InitializeComponent();

        IconClicked = new IconClickedCommand();
        // 中略 イベントハンドラ追加の処理
    }
}

これで、先のXAMLでの指定が機能する。

LeftClickCommand="{x:Bind IconClicked}"

プログラムの実行と停止

Unpackagedであることを確認してVisual Studioからデバッグでビルドと実行を行う。 最初に起動したウィンドウを閉じた後、NotifyIconを左クリックして再びウィンドウを表示できれば動作テストは完了。

Closedイベントに対するデフォルトの動作変えたから、アプリを停止させるにはVisual Studioから「デバッグの停止」を押すか、プロセスを直接killする必要がある。

成果物

https://github.com/dullwhale/winui3-notifyicon-test

認可の文脈におけるgroupとroleの違い

認可における普遍的な概念としてのgroupとroleは紛らわしい。 権限管理のフレームワークやライブラリにおいて、groupとroleの両方が同時に利用可能な場合、どのように使い分ければよいのか混乱するから自分の理解をメモしておく。 困ったことに、どちらを使っても同じ目的を達成できることが多く、人間が概念を理解し運用設計をしなければ使いこなせない。

原則

基本の考え方は次の3つに集約される。

  • groupはuserの集合である
  • roleはpermissionの集合である
  • permissionの付与は原則roleで単位で行う

この定義から、次のような2つの運用上の原則が導かれる。

  • 大多数に当てはまる通常の権限付与ではuserをgroupでまとめ、groupにroleを付与する。もしroleを付与できない場合はpermissionを付与する。
  • 例外的な権限付与は個別のroleをuserに追加する。

ここで、roleは必要に応じて追加されうるという点に注意せよ。 追加は永続的なものかもしれないし、一時的なものかもしれない。

理由

以降は理由や例を記載する。

大多数をカバーする非例外的、通常の権限付与はgroupを介して行うべきである。 具体的にはgroupを作成し、そのgroupに複数のuserを追加し、groupにロールを付与する。 もしシステム側の制約でroleを付与できない場合は代わりにpermissionを追加する。 このgroupは現実世界の役職などに合わせることが適切だろう。

stock_managerグループを作成し、そこに在庫管理する複数のuserを追加する。
stock_managerに在庫を管理するpermissionが定義されたroleを付与する。

この方法の利点は、運用上の手間が少ないことである。 改修などでpermissionなどが増えた場合、groupに紐づくroleを1回だけ修正すれば良い。

例外な権限付与の場合はgroupとは無関係に、必要なuserにだけroleを追加する。

ユーザAliceは、viewerのgroupに入っているから基本的には読み取りしかできないが、Aliceに個別のroleを付与して書き込み権限を追加で与える。

また、よりリスクが高い操作を行うために期限付きで一時的にroleを付与する使い方も考えられる。

普段は自分の認証に関わる情報を読み取ることができないが、二要素目の認証を行うことで一時的に読み取るためのroleを付与する。

次のstackoverflowのやりとりが参考になる。

https://stackoverflow.com/questions/7770728/group-vs-role-any-real-difference

Windows App SDK(WinUI3)でウィンドウを閉じられてもアプリケーションを動かし続ける

Window.Closedにイベントハンドラを追加し、そのイベントハンドラ内で引数として渡されたイベントのHandledプロパティをtrueに設定すればよい。

Windows App SDKのイベントの仕組み

Windows App SDKのイベント処理の仕組みは、Webフロントエンドのそれと似ている。

ここでは説明のため、一旦JavaScriptを考える。 同じイベントをlistenする複数のイベントハンドラが登録されている状況を想像せよ。 例えば<div>要素の中に<button>があり、それぞれclickイベントに対するハンドラが設定されているとする。 この状況でbuttonを押すと、通常は次の順番で両方のハンドラが呼び出される。

  1. buttonのclickイベントのハンドラ
  2. divのclickイベントのハンドラ

ところが、buttonのイベントハンドラ内でイベントを完全にconsumeして、div要素へのイベントの伝播を阻止することもできる。 詳しくはイベントバブリングやイベントプロパゲーションで調べよ。

話をWindows App SDKに戻す。 恐らくWindowベースclassのClosedイベントに反応するハンドラとして、「他にactiveなウィンドウが存在しなければアプリケーションを終了する」という処理が登録されている。 これを阻止するためには、同様にClosedイベントへ反応する優先度の高いハンドラを追加し、そのハンドラ内でイベントの伝播を止めてしまえばよい。 例えば、以下のようなコードが考えられる。

// Closedイベントに反応するイベントハンドラを追加
Window.Closed += (sender, args) =>
{
  // trueを設定することで、イベントの伝播を阻止。
  args.Handled = true;
};
コード中の`Window`は実際には継承したクラス/インスタンス名になる。
テンプレートから名前を変更していない場合は`MainWindow`だろう。