PowerShell で .NET のアセンブリ内の関数の動作確認

HogeHoge.exe の中のクラス Config の shared メソッド Load を呼んでみる。

test.ps1:

[reflection.assembly]::LoadFrom("bin\Release\HogeHoge.exe")

$data = [HogeHoge.Config]::Load("hogehoge.xml")

Write-Host("---")
Write-Host($data.ToString())
Write-Host("---")

あれ、もっと面倒だったような気がしたのだが、勘違いかな?

NYAGOS保守ノート: 欧州向けキーボード対応ふたたび(AltGR キー処理)

日本では馴染みがないのだが、ヨーロッパのキーボードには AltGR というキーがあるようだ。用途としては、英語圏にない文字等を入力する時にシフト的に用いるようだ。

それが入力できなくなったという issue を起案いただいた。

AltGR 対応は、以前、一度行ったのだが、当時は一文字キー入力は自前(zetamatta/go-getch)でやっていた。

nyagos 4.4 で Linux 対応をするにあたって、mattn/go-tty に切り替えたのだが、本件についてはその際対応漏れとなっていたようだ。(いや、実はうすうす気付いてはいたのだが、テスト協力者がいなくてな)

さて、go-tty の場合、どうやればいいだろうか。 というか、キーボードそのものがないのに、どうやって動作確認をとれば?

報告者に確認してみたところ、AltGR + 6 を押下すると本来はパイプライン記号(|)が入力されるべきところが、^[| (ESCAPE + | )がデータとしてきてしまっている。つまり、単に ESCAPE が多いだけではないか。

で、実際、go-tty のコードを拝見すると、Ctrlキーと Altキーを同時押しした時、ESCAPE + その文字の Unicode を返すような動作となっている。 報告者のリポートを見ると、「CTRL+ALT+6」でも同じ結果になったと言っている。 OS的には AltGR キーは Ctrl+Alt 同時押しと同じ扱いになっており、go-tty もちゃんと対応していると見てよいだろう。

よって、対応としては、ESCAPE + 1文字が来た時は、ESCAPE をカットすればよさそうである。 ただ、そうすると、他の ESCAPE 文字で表現される特殊キーのアサインに支障が発生する。 そこで未アサインのキーが来た場合、つまり、そのキーの内容をそのまま一行入力バッファに格納する場合のみ、先頭にある ESCAPE をカットするという風に対応した。

これでコミットして、AppVeyor のバイナリを報告者に使ってみていただいたところ Ok がもらえた。バッチリである。

続・GoでCygterm っぽいことをやりたいので、ソケットサーバーを書いている

アドバイスをいただきました(ありがとうございます)

試してみたところ、確かに

$ diff TERATERM-orig.INI TERATERM.INI
491c491
< EnableLineMode=on
---
> EnableLineMode=off

と変えたところ、プログラムは無修正で1文字単位での送受信ができるようになりました(なお、LocalEcho については、はじめから「LocalEcho=off」になってました)。

教えていただいた参考ページによると、ssh の場合とか、サーバーからの応答によっては最初からEnableLineMode=off になるみたいですね。

それと同じようなことを自動でやればよさそうです。nyag?os のTeraTerm 接続は長年の懸案ですが、これはもしかしていけるか!?

a[:0] と append の秘密

a = a[:0] は領域のサイズをリセットするが、a = make([]T,0,cap(a)) と違って、使っていたメモリブロックを再利用するため、allocation 回数を削減できる。

だが、旧a の領域が他で使われていないかを気にせず、無頓着に使うと append でおかしいことになる。たとえば

a := []string{ "a","b","c" }
b := a[:0]
b = append(b,"1")

fmt.Printf("%+v\n",a)

とすると a の内容が [1 b c] となる。これは append が領域を上書きで使用するためだ。

append 側で他所で使われているかチェックしてくれたらよさそうな話ではあるが、おそらく物理的に無理だろう。というのも参照カウンタ方式ではなく、マーク&スイープ方式の Garbage Collector を使っている場合、参照されている場所が自分を含めて2個以上あるか、調べようがないのだ。

となると、常に他で使われているという前提で append を実装しないと、上のような状況は避けられないわけだ。そういう append ちょっと作ってみよう。

package main

import (
    "fmt"
)

func appendStr(a []string, b string) []string {
    r := make([]string, len(a)+1)
    copy(r,a)
    r[len(a)] = b
    return r
}

func main() {
    a := []string{}
    for i := 0; i < 10; i++ {
        a = appendStr(a, fmt.Sprintf("%d", i))
    }
    fmt.Printf("%+v\n", a)
}

こんな実行効率わるそうなの、みんな使いたいと思います?結局、みんなオリジナル append を使いますよね? (でも、スクリプト言語では効率が悪くても平気でやってそうだなぁ)。

Go で Cygterm っぽいことをやりたいので、ソケットサーバーを書いている(勉強中)

続きの記事 あり)

serv.go :

package main

import (
    "fmt"
    "net"
    "os"
    "strings"
)

func handler(conn net.Conn) {
    defer conn.Close()

    var buffer [255]byte

    for {
        n, err := conn.Read(buffer[:])
        if err != nil {
            fmt.Fprintln(os.Stderr, err)
            return
        }
        text := string(buffer[:n])
        text = strings.Replace(text,"\r","\n",-1)
        conn.Write(buffer[:n])
        fmt.Printf("[%s]\n",text )
    }
}

func main1() error {
    ln, err := net.Listen("tcp", ":8080")
    if err != nil {
        return err
    }
    for {
        conn, err := ln.Accept()
        if err != nil {
            return err
        }
        go handler(conn)
    }
}

func main() {
    if err := main1(); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

go run serv.go すると、teratermtelnetlocalhost:8080 に接続できる。が、TeraTerm 側でEnter を入力しないと通信が届かない。これ、Enter なしで送受信するにはどうすればいいんだろう

("bufio".Reader) ReadString('\n') と ("bufio".Scanner) Scan() の違い

違い

  • ("bufio".Reader) ReadString('\n')
    • 改行をカットしない
  • ("bufio".Scanner) Scan()
    • 改行をカットする

改行コードのない最終行の扱い

最後の行が abcd(EOF) というテキストの場合

  • ("bufio".Reader) ReadString('\n')
    • "abcd",io.EOF という戻り値となる
  • ("bufio".Scanner) Scan()
    • Text() は "abcd" という戻り値を返し、そのScan() が false になる

Scanner の方は定番である

for sc.Scan() {
   line := sc.Text()
   fmt.Println(line)
}

という書き方で問題ないが、Reader の方は

for{
    line,err := r.ReadString('\n')
    if err != nil {
        break
    }
    fmt.Print(line)
}

だと、最後の行が抜けてしまう。

for{
    line,err := r.ReadString('\n')
    fmt.Print(line)
    if err != nil {
        break
    }
}

と書かないといけない。こう書くと Scanner の方が常に正しいように思われるが、Scanner は最終行の改行の有無がわからないという問題がある。