<ESC>O を押した後に挿入モードに入るのに一瞬のラグがある場合は ttimeoutlen を設定せよ

Macのターミナル等で

int main() {
}|

のようなコードを打った後 ( | がカーソル位置) <ESC>O を押すと1秒ぐらいのラグの後、挿入モードに入ることがある。

理由は <ESC>OA が UPカーソルに設定されているターミナルがあり ターミナルが A の入力まちになるのが問題のようだ。

set ttimeoutlen としたときに値が -1 とかだと1秒ぐらいの待ちが発生するので set ttimeoutlen=100 とすると解決する。

参考資料

terminal - Up arrow key code, why '^[[A' becomes '^[OA'? - Vi and Vim Stack Exchange

f:id:bamch0h:20200105000604p:plain

Windowsのウィンドウをキャプチャしたい (1)

ふと、Windowsのウィンドウをキャプチャしたいと思ったので調べてみた。

qiita.com

この記事が大変参考になる。上に重なっているウィンドウも一緒くたにキャプチャしたい場合はこの方法でいけそう。

なぜか、とりたいウィンドウだけを指定すると真っ黒になる場合もあり、うまく動かないみたいだった。

qiita.com

ここの記事によると、上記の方法は古く、まともに動かない場合もあるかも。みたいな感じで書かれているので 動かなくてもそういうものなのかな?と思っている。

OBS Studio のキャプチャの方法も一番初めのWin32APIを使う方法のようで、かつ同じようにうまくキャプチャできないウィンドウがあるので、本当にそういうものなのだと思う。

2021年1月22日 追記

Windows の 3 種類のスクリーンキャプチャ API を検証する - Qiita

最近、キャプチャ関係の記事が出てた。ここら辺を参考にしたらうまい感じにキャプチャできそう?

Windows XP でファイルの更新日付やサイズが欲しい場合は syscall.GetFileInformationByHandle() を使おう

はじめに

ひょんなことから Windows XP で ファイルの更新日付やサイズを取得して一覧で表示しないといけないミッションがありました。Windows 10 でそのようなプログラムをGoで書いていたので、それを使えばXPでも動くだろうと高をくくっていたら、File.Stat() 関数内でRuntimeエラーが発生して落ちてしまう事態に。調べてみると、File.Stat() 内で windows.GetFileInformationByHandleEx() を使用していて、それが XPではサポートされていない関数だったことがわかりました。

go/types_windows.go at master · golang/go · GitHub

もう少し調べてみると、windows.GetFileInformationByHandleEx() 関数で取得した値では、ファイルの更新日付やサイズを取得しておらず、その前段の windows.GetFileInfomationByHandle() 関数で取得しているようでした。

go/types_windows.go at master · golang/go · GitHub

今回はファイルの更新日付とサイズが取得できればいいので、File.Stat() を使うのをやめて、自前で windows.GetFileInformationByHandle() 関数を使って情報を取得するようにしました。

コード

ModTIme()Size() のメソッドを https://github.com/golang/go/blob/master/src/os/types_windows.go#L136, https://github.com/golang/go/blob/master/src/os/types_windows.go#L108 から拝借して、tinyStat() として取得できるようにしました。

package main

import (
    "fmt"
    "os"
    "syscall"
    "time"
)

type fileStat struct {
    LastWriteTime syscall.Filetime
    FileSizeHigh  uint32
    FileSizeLow   uint32
}

func (fs *fileStat) ModTime() time.Time {
    return time.Unix(0, fs.LastWriteTime.Nanoseconds())
}

func (fs *fileStat) Size() int64 {
    return int64(fs.FileSizeHigh)<<32 + int64(fs.FileSizeLow)
}

func tinyStat(filename string) (*fileStat, error) {
    fp, err := os.Open(filename)
    if err != nil {
        return nil, err
    }

    var d syscall.ByHandleFileInformation
    h := syscall.Handle(fp.Fd())
    err = syscall.GetFileInformationByHandle(h, &d)
    if err != nil {
        return nil, err
    }
    return &fileStat{
        LastWriteTime: d.LastWriteTime,
        FileSizeHigh:  d.FileSizeHigh,
        FileSizeLow:   d.FileSizeLow,
    }, nil
}

func main() {
    fs, err := tinyStat("main.go")
    if err != nil {
        panic(err)
    }

    fmt.Printf("ModTime %v, Size %v bytes\n", fs.ModTime().Format("2006-01-02 03:04:05"), fs.Size())
}

まとめ

ファイルの更新日付とサイズをWindows XPでも取得できるようにしました。 FileInfo インターフェースをすべて実装すれば、よりシームレスに使えるのではないかと思いますが 今回はそこまでのものは必要なかったので、これで十分でした。

GoのSliceでハマった話

Goで配列の順列組み合わせを列挙するプログラムを書いているときに、 配列のサイズが大きくなると、同じパターンが列挙されてしまって ちゃんと動かないという問題が発生した。

結論としては、appendの仕様を間違って理解していたために 同じSliceに対して処理をしてしまって、間違った挙動になっていた というのがオチだった。

問題のソースは以下の通り

package main

import (
    "fmt"
)

func remove(a []int, i int) []int {
    o := make([]int, 0)
    for j := 0; j < len(a); j++ {
        if j == i {
            continue
        }
        o = append(o, a[j])
    }
    return o
}

func permutation(a []int, x []int, xx [][]int) [][]int {
    if len(a) == 0 {
        xx2 := append(xx, x)
        fmt.Printf("%010p, cap(%2d), %v\n", x, cap(x), x)
        for _, x2 := range xx2 {
            fmt.Printf("%010p, cap(%2d), %v\n", x2, cap(x2), x2)
        }
        fmt.Scanln()
        return xx2
    }

    for i, c := range a {
        x2 := append(x, c)
        fmt.Printf("x =%010p, cap(%2d), %v\n", x, cap(x), x)
        fmt.Printf("x2=%010p, cap(%2d), %v\n", x2, cap(x2), x2)
        fmt.Println()
        xx = permutation(remove(a, i), x2, xx)
    }
    return xx
}

func main() {
    a := []int{1, 2, 3, 4, 5, 6, 7}
    x := make([]int, 0)
    xx := make([][]int, 0)
    xx = permutation(a, x, xx)
    for i, e := range xx {
        fmt.Println(e)
        if i == 10 {
            break
        }
    }
}

出力はこんな感じになる。fmt.Scanlnで出力を止めているので、実際にはこんな感じはならなけど。

x =0x00005904e8, cap( 0), []
x2=0xc000058058, cap( 1), [1]

x =0xc000058058, cap( 1), [1]
x2=0xc0000580d0, cap( 2), [1 2]

x =0xc0000580d0, cap( 2), [1 2]
x2=0xc000056120, cap( 4), [1 2 3]

x =0xc000056120, cap( 4), [1 2 3]
x2=0xc000056120, cap( 4), [1 2 3 4]

x =0xc000056120, cap( 4), [1 2 3 4]
x2=0xc000088240, cap( 8), [1 2 3 4 5]

x =0xc000088240, cap( 8), [1 2 3 4 5]
x2=0xc000088240, cap( 8), [1 2 3 4 5 6]

x =0xc000088240, cap( 8), [1 2 3 4 5 6]
x2=0xc000088240, cap( 8), [1 2 3 4 5 6 7]

0xc000088240, cap( 8), [1 2 3 4 5 6 7]
0xc000088240, cap( 8), [1 2 3 4 5 6 7]

x =0xc000088240, cap( 8), [1 2 3 4 5]
x2=0xc000088240, cap( 8), [1 2 3 4 5 7]

x =0xc000088240, cap( 8), [1 2 3 4 5 7]
x2=0xc000088240, cap( 8), [1 2 3 4 5 7 6]

0xc000088240, cap( 8), [1 2 3 4 5 7 6]
0xc000088240, cap( 8), [1 2 3 4 5 7 6]
0xc000088240, cap( 8), [1 2 3 4 5 7 6]

私の認識では appendは常に新しく配列を作成してくれるものだと思っていたが、実際はcapの上限までは同じ配列を使用する。 上限まで達するとcapを現在の2倍に広げてから末尾に追加する。という挙動になる。 配列のコピーが発生しているものだと思っていたので、再起関数内でappendの後に元の配列を操作するような処理を入れており それが悪さをして、append先の配列の内容まで変わってしまっている。

これを回避するには、xx に追加するときに x の内容を別の配列にコピーしてから追加すること。

package main

import (
    "fmt"
)

func remove(a []int, i int) []int {
    o := make([]int, 0)
    for j := 0; j < len(a); j++ {
        if j == i {
            continue
        }
        o = append(o, a[j])
    }
    return o
}

func permutation(a []int, x []int, xx [][]int) [][]int {
    if len(a) == 0 {
        x2 := make([]int, len(x)) // 別の配列を作って
        copy(x2, x) // コピー
        xx2 := append(xx, x2) // コピーした配列をxxに追加する
        fmt.Printf("%010p, cap(%2d), %v\n", x, cap(x), x)
        for _, x2 := range xx2 {
            fmt.Printf("%010p, cap(%2d), %v\n", x2, cap(x2), x2)
        }
        fmt.Scanln()
        return xx2
    }

    for i, c := range a {
        x2 := append(x, c)
        fmt.Printf("x =%010p, cap(%2d), %v\n", x, cap(x), x)
        fmt.Printf("x2=%010p, cap(%2d), %v\n", x2, cap(x2), x2)
        fmt.Println()
        xx = permutation(remove(a, i), x2, xx)
    }
    return xx
}

func main() {
    a := []int{1, 2, 3, 4, 5, 6, 7}
    x := make([]int, 0)
    xx := make([][]int, 0)
    xx = permutation(a, x, xx)
    for i, e := range xx {
        fmt.Println(e)
        if i == 10 {
            break
        }
    }
}

出力を確認すると、以下のような感じになる。

x =0x00005904e8, cap( 0), []
x2=0xc0000120b0, cap( 1), [1]

x =0xc0000120b0, cap( 1), [1]
x2=0xc000012110, cap( 2), [1 2]

x =0xc000012110, cap( 2), [1 2]
x2=0xc00000a460, cap( 4), [1 2 3]

x =0xc00000a460, cap( 4), [1 2 3]
x2=0xc00000a460, cap( 4), [1 2 3 4]

x =0xc00000a460, cap( 4), [1 2 3 4]
x2=0xc00000e3c0, cap( 8), [1 2 3 4 5]

x =0xc00000e3c0, cap( 8), [1 2 3 4 5]
x2=0xc00000e3c0, cap( 8), [1 2 3 4 5 6]

x =0xc00000e3c0, cap( 8), [1 2 3 4 5 6]
x2=0xc00000e3c0, cap( 8), [1 2 3 4 5 6 7]

0xc00000e3c0, cap( 8), [1 2 3 4 5 6 7]
0xc00000e400, cap( 7), [1 2 3 4 5 6 7]

x =0xc00000e3c0, cap( 8), [1 2 3 4 5]
x2=0xc00000e3c0, cap( 8), [1 2 3 4 5 7]

x =0xc00000e3c0, cap( 8), [1 2 3 4 5 7]
x2=0xc00000e3c0, cap( 8), [1 2 3 4 5 7 6]

0xc00000e3c0, cap( 8), [1 2 3 4 5 7 6]
0xc00000e400, cap( 7), [1 2 3 4 5 6 7]
0xc00000e440, cap( 7), [1 2 3 4 5 7 6]

二次元配列の各要素のアドレスが別々のアドレスをさすようになった。

open62541のサンプルをMinGWで動かす

今日は open62541 のサーバーのサンプルを MinGW で動かす。

ファイル構成はこんな感じとする

./
├ open62541 // git clone https://github.com/open62541/open62451 でとってきたファイル群
└ myServer.c // 今回動かすサーバーサンプル

まずは ライブラリをビルド

git clone https://github.com/open62541/open62541
cd open62541
mkdir build
cd build
cmake .. -G "MSYS Makefiles"

MINGW Makefiles を使うとなぜかエラーになったので、 MSYS Makefiles を使った

プロジェクトルートに戻って、 myServer.c を作成する

#include <open62541/plugin/log_stdout.h>
#include <open62541/server.h>
#include <open62541/server_config_default.h>

#include <signal.h>
#include <stdlib.h>

static volatile UA_Boolean running = true;
static void stopHandler(int sig) {
  UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "received ctrl-c");
  running = false;
}

int main(void) {
  signal(SIGINT, stopHandler);
  signal(SIGTERM, stopHandler);

  UA_Server *server = UA_Server_new();
  UA_ServerConfig_setDefault(UA_Server_getConfig(server));

  UA_StatusCode retval = UA_Server_run(server, &running);

  UA_Server_delete(server);
  return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}

んで、ビルド。include するディレクトリが多いので注意が必要なのと、mingwの場合は ws2_32 の指定が必要。というか、Windowsだったら、ws2_32の指定が必要なのかな。

gcc -I ./open62541/include -I ./open62541/plugins/include -I ./open62541/build/src_generated -I ./open62541/arch -I ./open62541/deps -L ./open62541/build/bin -std=c99 myServer.c -lopen62541 -lws2_32 -o myServer

これで、myServer.exe が出来上がる。

このサンプルでは、オリジナルのノードデータを追加していないので、クライアントから見えるのはサーバーのシステム情報ノードのみ。

open62541のサンプルを動かす

前回の記事でopen62541のビルドに成功して、 open62541.lib が生成できるのは確認できた。 けど、これができたからといって実際には使えないので、このライブラリを使ってexeを作る必要がある。

んで、githubのページのREADMEにはサンプルが示してあって、今回はそれを動かしてみる。

https://github.com/open62541/open62541/tree/v1.1-dev#examples

#include <stdio.h>
#include <open62541/client.h>
#include <open62541/client_config_default.h>
#include <open62541/client_highlevel.h>

int main(int argc, char* argv[])
{
    /* Create a client and connect */
    UA_Client* client = UA_Client_new();
    UA_ClientConfig_setDefault(UA_Client_getConfig(client));
    UA_StatusCode status = UA_Client_connect(client, "opc.tcp://localhost:4840");
    if (status != UA_STATUSCODE_GOOD) {
        UA_Client_delete(client);
        return status;
    }

    /* Read the value attribute of the node. UA_Client_readValueAttribute is a
    * wrapper for the raw read service available as UA_Client_Service_read. */
    UA_Variant value; /* Variants can hold scalar values and arrays of any type */
    UA_Variant_init(&value);
    status = UA_Client_readValueAttribute(client, UA_NODEID_STRING(1, "the.answer"), &value);
    if (status == UA_STATUSCODE_GOOD &&
        UA_Variant_hasScalarType(&value, &UA_TYPES[UA_TYPES_INT32])) {
        printf("the value is: %i\n", *(UA_Int32*)value.data);
    }

    /* Clean up */
    UA_Variant_deleteMembers(&value);
    UA_Client_delete(client); /* Disconnects the client internally */
    return status;
}

ちなみに、私はC言語初心者なので、大分基本的な部分から入っていくことになる。

今回は v1.1-dev タグを使っていく。 v1.0 タグだとなぜかビルドできなかった。

git checkout refs/tags/v1.1-dev

open62541.lib のビルド方法は前回の記事を参照のこと。今回は Release - x64 でビルドしたものを使用。

C++のコンソールソリューションを作成する。

上のコードをコピペする。

インクルードパスが足りていないので、足す。

  • <open62541のgitリポジトリがクローンされているパス>\arch
  • <open62541のgitリポジトリがクローンされているパス>\build\src_generated
  • <open62541のgitリポジトリがクローンされているパス>\plugins\include
  • <open62541のgitリポジトリがクローンされているパス>\include

まだエラーがでる。error C2664: 'UA_LocalizedText UA_LOCALIZEDTEXT(char *,char *)': 引数 1 を 'const char [6]' から 'char *' へ変換できません。 とかいうの。

なので、 その他のオプション のところに /Zc:strictStrings- を足す。

エラーは出なくなるが、リンカが通らないので、リンカオプションを足す。

まずは、 追加のライブラリ ディレクトリopen62541.lib があるディレクトリを指定する。

追加の依存ファイルに oepn62541.lib を足す。ただ、これだけだとまだ足りなくて、 ws2_32.lib も必要なので併記しておく。

これでビルドは通るようになる。

ただ、リンクワーニング LINK : warning LNK4098: defaultlib 'LIBCMT' は他のライブラリの使用と競合しています。/NODEFAULTLIB:library を使用してください。 が表示される。

ランタイムライブラリ/MT にすればこの問題は解決する。(この解決策がいいのかは不明)