PowershellでAsyncCallbackを使うにはどうしたらよいか

PowershellUdp双方向通信を行うスクリプトを書こうとしてハマった

前提

メインスレッド Udp送信担当
サブスレッド Udp受信担当
受信はメインスレッド内でUdpClient.BeginReceiveを呼び出して非同期処理にする。

コールバック関数を定義し、 Receiveが値を受け取った時に関数呼び出し(受信データを基にメインスレッドのフォーム更新や結果のUdp送信など)を実行して、再びUdpClient.BeginReceiveを呼ぶ

いわゆるAPM(Asynchronous Programming Model)ってやつです

スクリプト

受信開始とコールバックのところのみ

(略)
# UDP受信待ち受け
function udpreceiver_start {
    param(
        $ipaddress = "127.0.0.1", #バインドするアドレス
        $port = 4444  #バインドするポート
    )

    # ローカルエンドポイント定義
    $localEP = new-object System.Net.IPEndPoint([System.Net.IPAddress]::Parse($ipaddress),$port)
    # リモートエンドポイント定義
    #$remoteEP = new-Object System.Net.IPEndPoint([System.Net.IPAddress]::Any, 0);
    # UDPサーバーを生成
    $udprecv = new-Object System.Net.Sockets.UdpClient($localEP)

    # 非同期的なデータ受信を開始する
    Write-Host "BeginReceive!"
    $handle = $udprecv.BeginReceive([System.AsyncCallback]${function:udpreceiver_callback},$udprecv);
}

# UDP受信callback
function udpreceiver_callback {
    param(
        $ar
    )

    Write-host "Callback!!"

    $udprecv = [System.Net.Sockets.UdpClient]{$ar.AsyncState};

    # 非同期受信を終了する
    [System.Net.IPEndPoint]$remoteEP = $null;
    try{
        $buf = $udprecv.EndReceive($ar,[ref]$remoteEP);
    }
    catch [System.Net.Sockets.SocketException] {
        Write-Host ("受信エラー({0}/{1})" -f $_.Message, $_.ErrorCode)
        return;
    }
    catch [ObjectDisposedException] {
        //すでに閉じている時は終了
        Write-Host "Socketは閉じられています。"
        return;
    }

    $str = [System.TexT.Encoding]::UTF8.GetString($buf)

    Write-host "データを受信しました"
    Write-Host ("受信したデータ:{0}" -f $str)
    Write-Host ("送信元アドレス:{0}/ポート番号:{1}" -f $remoteEP.Address, $remoteEP.Port)

    //再びデータ受信を開始する
    $udprecv.BeginReceive([System.AsyncCallback]${function:udpreceiver_callback},$udprecv);
}
(略)
udpreceiver_start
(略)

問題点

UDPでデータを受信すると・・・

f:id:hightoro:20170816234635p:plain

_人人 人人_
> 突然の死 <
 ̄YYYY

原因分析1

コールバックとして普通の関数を[System.AsyncCallback]でキャストしたものを渡したのが原因だろうかと思って、そこをC#で書いて渡してみる。

(略)
# UDP受信待ち受け
function udpreceiver_start {
    param(
        $ipaddress = "127.0.0.1", #バインドするアドレス
        $port = 4444  #バインドするポート
    )

    # ローカルエンドポイント定義
    $localEP = new-object System.Net.IPEndPoint([System.Net.IPAddress]::Parse($ipaddress),$port)
    # リモートエンドポイント定義(空:後で使う)
    $remoteEP = new-Object System.Net.IPEndPoint([System.Net.IPAddress]::Any, 0);
    # UDPサーバーを生成
    $udprecv = new-Object System.Net.Sockets.UdpClient($localEP)

    # 非同期的なデータ受信を開始する #無理だった
    Write-Host "BeginReceive!"
    #$handle = $udprecv.BeginReceive([System.AsyncCallback]${function:udpreceiver_callback},$udprecv);
    $handle = $udprecv.BeginReceive([Udp]::udpreceiver_callback,$udprecv);
}


Add-type @"
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

public static class Udp
    {
        public static void udpreceiver_callback(IAsyncResult ar=null)
        {
            Console.WriteLine("Callback!");

            var udprecv = (System.Net.Sockets.UdpClient)ar.AsyncState;
            byte[] buf;

            // 非同期受信を終了する
            var remoteEP = new System.Net.IPEndPoint(System.Net.IPAddress.Any, 0);
            try
            {
                buf = udprecv.EndReceive(ar, ref remoteEP);
            }
            catch(System.Net.Sockets.SocketException ex)
            {
                Console.WriteLine("受信エラー({0}/{1})",ex.Message, ex.ErrorCode);
                return;
            }
            catch(ObjectDisposedException)
            {
                //すでに閉じている時は終了
                Console.WriteLine("Socketは閉じられています。");
                return;
            }

            var str = System.Text.Encoding.UTF8.GetString(buf);

            Console.WriteLine("データを受信しました");
            Console.WriteLine("受信したデータ:{0}", str);
            Console.WriteLine("送信元アドレス:{0}/ポート番号:{1}", remoteEP.Address, remoteEP.Port);

            //再びデータ受信を開始する
            Console.WriteLine("BeginReceive!");
            udprecv.BeginReceive(udpreceiver_callback, udprecv);
        }
    }
"@
(略)
udpreceiver_start
(略)

f:id:hightoro:20170816234635p:plain

_人人 人人_
> 突然の死 <
 ̄YYYY

ダメでした

対応策

全部C#で書く(爆)

(略)
Add-type @"
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

public static class Udp
    {
        public static void udpreceiver_start(
            //AsyncCallback callback,
            string ipaddress = "127.0.0.1", //バインドするアドレス
            int port = 4445  //バインドするポート
        )
        {
            //ローカルエンドポイント定義
            var localEP = new System.Net.IPEndPoint(System.Net.IPAddress.Parse(ipaddress), port);
            //リモートエンドポイント定義(空:後で使う)
            var remoteEP = new System.Net.IPEndPoint(System.Net.IPAddress.Any, 0);
            //UDPサーバーを生成
            var udpClient = new System.Net.Sockets.UdpClient(localEP);

            //非同期的なデータ受信を開始する
            Console.WriteLine("BeginReceive!");
            //BeginReceive2(callback, udpClient);
            udpClient.BeginReceive(udpreceiver_callback, udpClient);
        }

        public static void udpreceiver_callback(IAsyncResult ar=null)
        {
            Console.WriteLine("Callback!");

            var udprecv = (System.Net.Sockets.UdpClient)ar.AsyncState;
            byte[] buf;

            // 非同期受信を終了する
            var remoteEP = new System.Net.IPEndPoint(System.Net.IPAddress.Any, 0);
            try
            {
                buf = udprecv.EndReceive(ar, ref remoteEP);
            }
            catch(System.Net.Sockets.SocketException ex)
            {
                Console.WriteLine("受信エラー({0}/{1})",ex.Message, ex.ErrorCode);
                return;
            }
            catch(ObjectDisposedException)
            {
                //すでに閉じている時は終了
                Console.WriteLine("Socketは閉じられています。");
                return;
            }

            var str = System.Text.Encoding.UTF8.GetString(buf);

            Console.WriteLine("データを受信しました");
            Console.WriteLine("受信したデータ:{0}", str);
            Console.WriteLine("送信元アドレス:{0}/ポート番号:{1}", remoteEP.Address, remoteEP.Port);

            //再びデータ受信を開始する
            udprecv.BeginReceive(udpreceiver_callback, udprecv);
        }

    }
"@
(略)
[Udp]::udpreceiver_start()
(略)

これだとうまくいきました。

改良版

PowershellのUdpClientではBeginReceive()メソッドを呼び出さず、C#で記述したstaticメソッド[Udp]::BeginReceiveの中で呼び出すようにすると、 問題点1を解決しつつ、startのメソッドをpowershellにできます。

(略)
# UDP受信待ち受け
function udpreceiver_start {
    param(
        $ipaddress = "127.0.0.1", #バインドするアドレス
        $port = 4445  #バインドするポート
    )

    # ローカルエンドポイント定義
    $localEP = new-object System.Net.IPEndPoint([System.Net.IPAddress]::Parse($ipaddress),$port)
    # リモートエンドポイント定義(空:後で使う)
    $remoteEP = new-Object System.Net.IPEndPoint([System.Net.IPAddress]::Any, 0);
    # UDPサーバーを生成
    $udprecv = new-Object System.Net.Sockets.UdpClient($localEP)

    # 非同期的なデータ受信を開始する
    Write-Host "BeginReceive!"
    #$handle = $udprecv.BeginReceive([System.AsyncCallback]${function:udpreceiver_callback},$udprecv);
    $handle = [UDP]::BeginReceive($null,$udprecv)
}

Add-type @"
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

public static class Udp
    {
        public static void udpreceiver_callback(IAsyncResult ar)
        {
            Console.WriteLine("Callback!");

            var udprecv = (System.Net.Sockets.UdpClient)ar.AsyncState;
            byte[] buf;

            // 非同期受信を終了する
            var remoteEP = new System.Net.IPEndPoint(System.Net.IPAddress.Any, 0);
            try
            {
                buf = udprecv.EndReceive(ar, ref remoteEP);
            }
            catch(System.Net.Sockets.SocketException ex)
            {
                Console.WriteLine("受信エラー({0}/{1})",ex.Message, ex.ErrorCode);
                return;
            }
            catch(ObjectDisposedException)
            {
                //すでに閉じている時は終了
                Console.WriteLine("Socketは閉じられています。");
                return;
            }

            var str = System.Text.Encoding.UTF8.GetString(buf);

            Console.WriteLine("データを受信しました");
            Console.WriteLine("受信したデータ:{0}", str);
            Console.WriteLine("送信元アドレス:{0}/ポート番号:{1}", remoteEP.Address, remoteEP.Port);

            //再びデータ受信を開始する
            udprecv.BeginReceive(udpreceiver_callback, udprecv);
        }
        // UdpClient.BeginReceive(callback)を呼び出す関数
        public static System.IAsyncResult BeginReceive(
            Action<IAsyncResult> callback,
            System.Net.Sockets.UdpClient udpClient
        )
        {
            return udpClient.BeginReceive( _ => {udpreceiver_callback(_);}, udpClient);
        }
    }
"@

udpreceiver_start

ちなみにpowershellで書いた関数をコールバック内で呼ぶだけで死ぬので、コールバックはC#で書くしかなかった。どうしようもなかった。

参考にしたリンク先

UDPによりデータの送受信を行う: .NET Tips: C#, VB.NET

.dotfilesに.emacs.dを結合する

はじめに

前略

無知な私は.dotfilesと.emacs.dを別々のリポジトリで管理していたので
いい加減に統合したいと思います

早々

いろいろ調べて得られた知見(セオリー)としては
1.別ブランチに移動してコピー
2.masterにマージ
という様にブランチを一手間経由するのが確実で良いようです

1.別ブランチに移動してコピー

Emacsブランチの中身になるリモートリポジトリを登録する.

.dotfilesディレクトリに移動し,emacsリモートリポジトリを新しいリモート先として登録

$ git remote add Emacs git@github.com:hightoro/emacs

確認

$ git remote -v

フェッチして,新しいブランチを作成する

Emacsブランチに中身をぶちまける

$ git fetch Emacs
$ git checkout -b Emacs Emacs/master

これで,ブランチが切り替わり,ディレクトリの中身がemacs.gitのものになります

確認

$ git branch -a

2.masterにマージする

git filter-branchを使用してサブディレクトリ.emacs.dを作成

すこしだけこのコマンドの解説を読みました

git filter-branchはコミット履歴に大幅な改変を加えたいときに使用するコマンドだそうです

gitではコミット履歴は(他のプロジェクトメンバに公開して供するまでの間は)
自由に改変すべきだという思想があるそうです
そのためにgit rebaseや各種オプションが用意されているわけです

そしてこのgit filter-branchはその中でも「複数のコミットに対して,
ある種の改変を行いたい」場合に使用される最終兵器だそうです

今回はgit filter-branch --tree-filterを使用します
git filter-branch --tree-filterはこれまでの既存のコミット履歴に対して,
過去のある時点に何らかの操作を適応し、そこから現在までのコミット履歴を一括して改変するものらしいです

ではこれまでの./で行ったコミットをすべてサブディレクトリ.emacs.d/の中で起こったことにしましょう

$ git filter-branch -f --tree-filter "mkdir .emacs && git mv -k {,.[\!.],..[\!.]}* .emacs.d/"

mkdirした.emacs.dの中にぶちまけた中身をすべて放りこんだ上で,これまでにコミット履歴の一部が改変されます

Emacsブランチをmasterブランチにマージ

最後にEamcsブランチをmasterブランチにマージします

$ git checkout master
$ git merge Eamcs 

これで.emacs.dサブディレクトリにリモートリポジトリが追加され,
コミット履歴にもリモートリポジトリemacsのこれまでのコミット履歴がマージされる

参考:

Git - 歴史の書き換え

Githubさん,ごめんなさい!複数リポジトリを一つにまとめる方法 - Qiita

git リポジトリを歴史も含めて親に取り込んでまとめる | deadwood

cygwin上に環境構築

はじめに

ただのメモです
LinuxMintで構築してきたterminal環境をcygwin上に構築したかった

セットアップ

64bit版のsetup.exeを公式よりダウンロード
起動して進めて行く
パッケージのアクセスサーバーはなんとなくIIJさんのサーバーを選択

インストールする

ただしapt-cygに必要なものだけ

apt-cygをダウンロード

公式ではなく、forkしたものをダウンロード(公式の更新は止まってる?)
https://github.com/kou1okada/apt-cyg

普通にブラウザ上からダウンロード
これをCygwinインストールフォルダ内の
/usr/local/binに配置
(firefoxでDLするとapt-cyg.txtになってしまったのでrename)

cygwin上で 

$ chmod 755 /usr/local/bin/apt-cyg

これでapt-cygが動く
その後、apt-cygのアクセス先をIIJのサーバに変更する

$ apt-cyg -m ftp://ftp.iij.ad.jp/pub/cygwin/ update

よし! gitを落とそう!

$ apt-cyg install git

git submodule

git-submoduleを使うには、gettexのライブラリが必要
と言う事でapt-cygでインストールしましょう

$ apt-cyg install gettext

これでgit submodule initなどが正常に動くようになります

今回は以上

参考:
http://dqn.sakusakutto.jp/2013/12/64bit_cygwin_apt-cyg.html

LinuxMint17(MATE版)をVirutalboxに導入する際のメモ1

インストール

指示に従って、ポチポチとクリック。virtualboxの設定などは割愛
(スナップショット) (忘れた)

更新

おなじみのコマンド

 $ sudo apt-get update
 $ sudo apt-get dist-upgrade

結構待った。
再起動
(スナップショット)

Guest Additional

必要なパッケージを入れる

 $ sudo apt-get install build-essential module-assistant

kernel ヘッダファイルへのシンボリックリンク作成

 $ sudo m-a prepare

GuestAdditionalを起動してコンパイル

 $ sudo ./VBoxLinuxAdditions.run

再起動 (スナップショット)

日本語化

ibus-mozcよりもfcitx-mozcを薦めているサイト様があったので、試しに

im-configをインストールする必要があると思ったけど、METAには最初から入っていた

 $ sudo apt-get install im-config

「Fcitx」と「Mozc」をインストール

 $ sudo apt-get install fcitx fcitx-mozc fcitx-frontend-gtk2 fcitx-frontend-gtk3 fcitx-frontend-qt4 fcitx-frontend-qt5 fcitx-ui-classic fcitx-config-gtk mozc-utils-gui

Fcitx を有効に。 $ im-config -n fcitx (再起動)
(スナップショット)

参考:
http://linuxmintjp.jimdo.com/%E6%97%A5%E6%9C%AC%E8%AA%9E%E5%8C%96/#.U47WdJWKCUk
http://www.ice.is.kit.ac.jp/~umehara/misc/comp/20110723.html

GADT記法+tagをC++で

GADTとは

Generalized Algebraic DataTypes(GADT)と言うHaskellの記法(?)を知りました

Haskellの入門書などで出てくるMaybe型は以下のような定義になっています

data Maybe a = Nothing | Just a

これをGADTで書くと以下のようになるそうです

data Maybe a where
    Nothing :: Maybe a
    Just    :: a -> Maybe a

GADTで面白いのは、値コンストラクタがただの関数へと変化し、
戻り値型のMaybe型が直和型ではなくなっている(ように見える)ところです

またGADTの本来の特徴は多相型における値コンストラクタ(や一部の関数)
の戻り値型の取る型引数を任意の型に固定できる点です

先ほどのMaybe型を改造します
改造箇所は2型引数を取り、第1引数ではコンストラクタ毎の
識別用の型(いわゆる型tag)を付けるようにできます

--tag型
data NothingT
data JustT

--改・Maybe型
data Maybe' t a where
    Nothing :: Maybe' NothingT a 
    Just    :: a -> Maybe' JustT a 

こんな事ができると何がうれしいのでしょう?

まずパターンマッチを考えます
通常のMaybe型では以下のようになります

func :: (Show a) => Maybe a -> String
func (Just x)    = "just " ++ show x
func Nothing     = "Nothing"

これがMaybe'型では以下のようになります

func :: (Show a) => Maybe' tag a -> String
func (Just x)    = "Just " ++ show x
func Nothing     = "Nothing!"

あれ?変わってなくね?

と、思いがちになるのですが
Maybe'型であればWarningを出さずに
pattern matchを制限する事ができるようになります

例えば

func :: (Show a) => Maybe' t a -> String
func (Just x)    = "Just " ++ show x
--func Nothing     = "Nothing!"

のようにNothingをコメントアウトします
するとGHCでは

prog.hs:11:1: Warning: 
                Pattern match(es) are non-exhaustive
                In an equation for ‘func’: Patterns not matched: Nothing'

のように警告がでて怒られます

引数のMaybe型がJust aなのかNothingなのかはMaybe型の引数で判別できないため
Nothingで生成したMaybe型を関数に渡すところまでは
コンパイル時にチェックして問題なく通る(※)
→実行時に死ぬと言う事になります

となるとpattern matchにない値コンストラクタ
コンパイル時に検出して欲しいという要求がありそうです
GADTではこれを実現できます

先ほどの関数を以下のように書き換えます

func :: (Show a) => Maybe' JustT a -> String
func (Just x)    = "Just " ++ show x
--func Nothing     = "Nothing!"

何処が変わったかというと、1行目のMaybe' t aMaybe' JustT aに変わっています
これでWarningが出なくなります。
(この状態でコメントアウトを解除してもNothingが型エラーになります)

このようにパターンマッチ時にそもそも選択したくないマッチ条件を
排除するために利用されるようです

  • (※)ちなみにMaybe型はもともとshow Nothingでaの型が判別できないので
    (こちらの意図とは異なるものの)ちゃんとコンパイルエラーになります
    (Phantom-typeというコンパイル時型チェックの記法のようです)

本題

このGADT記法+tagのMaybeをC++で再現してみます

{-#LANGUAGE GADTs #-}

--tag
data NothingT
data JustT

-- data Maybe
data Maybe' t a where
    Nothing :: Maybe' NothingT a 
    Just    :: a -> Maybe' JustT a 

-- instance class show
instance (Show a) => Maybe' tag a where
    show (Just x) = "Just " ++ show x
    show Nothing  = "Nothing!"

main = do
    --putStrLn $ show Nothing
    putStrLn $ show (Just 42)
#include<iostream>

// tag
struct NothingT{};
struct JustT{};

// data Maybe
template<typename TAG,typename A> struct Maybe
{ A _; Maybe(A a):_(a){} };
template<typename T>
auto Just(T a){ return Maybe<JustT,T>(a); }
template<typename T=int> // 匿名型のままにはできない・・・
auto Nothing(){ return Maybe<NothingT,T>(0); }

// instance class show
template<typename T>
std::string show(Maybe<JustT,T> a)
{return "Just "+std::to_string(a._);}
template<typename T>
std::string show(Maybe<NothingT,T>)
{static_assert(std::is_same<T,void>::value,"can't show Nothing"); return "";}

int main()
{
    //show( Nothing() );
    show( Just(1)   );
}

http://melpon.org/wandbox/permlink/IhrmT6PWKLYN424T

参考:
http://faithandbrave.hateblo.jp/entry/20111201/1322718742