2016/09/30


http://qiita.com/shuetsu@github/items/ac21e597265d6bb906dc

orelang を Java で実装してみた

わりとよくある JSON ベースの lisp っぽいインタープリタの実装ですが、コードを見ていてもよくわからなかったので自分で実装しなおしてみました。

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "os"
)

func eval(env map[string]interface{}, v interface{}) interface{} {
    if vl, ok := v.([]interface{}); ok {
        return doRun(env, vl)
    }
    return v
}

func doRun(env map[string]interface{}, v []interface{}) interface{} {
    var r interface{}

    mn := v[0].(string)
    switch mn {
    case "step":
        for _, vi := range v[1:] {
            r = doRun(env, vi.([]interface{}))
        }
    case "until":
        for {
            c := eval(env, v[1])
            if c.(bool== true {
                break
            }
            r = doRun(env, v[2].([]interface{}))
        }
    case "get":
        return env[eval(env, v[1]).(string)]
    case "set":
        env[eval(env, v[1]).(string)] = eval(env, v[2])
        return v[2]
    case "=":
        return eval(env, v[1]).(float64== eval(env, v[2]).(float64)
    case "+":
        return eval(env, v[1]).(float64+ eval(env, v[2]).(float64)
    default:
        panic("Unknown operation: " + fmt.Sprint(v))
    }
    return r
}

func main() {
    source := `
["step",
  ["set", "i", 10],
  ["set", "sum", 0],
  ["until", ["=", ["get", "i"], 0], [
    "step",
    ["set", "sum", ["+", ["get", "sum"], ["get", "i"]]],
    ["set", "i", ["+", ["get", "i"], -1]]
  ]],
  ["get", "sum"]
]`

    var v interface{}
    err := json.Unmarshal([]byte(source), &v)
    if err != nil {
        log.Fatal(err)
    }

    env := make(map[string]interface{})

    defer func() {
        err := recover()
        if err != nil {
            fmt.Fprintln(os.Stderr, err)
        }
    }()
    fmt.Println(doRun(env, v.([]interface{})))
}

自分で書いた割に panic 前提なのが気に入らないし、そもそも orelang って同じ名前の言語持ってる。

GitHub - mattn/orelang: 俺言語

README.md orelang 俺言語 プログラミング言語の作り方 プログラミング言語の作り方(2) プログラミング言語の作り方(3) プログラミング言語の作り方(4) プログラミング言語の作り方...

http://github.com/mattn/orelang
みんなのGo言語【現場で使える実践テクニック】 みんなのGo言語【現場で使える実践テクニック】
松木雅幸, mattn, 藤原俊一郎, 中島大一, 牧 大輔, 鈴木健太
技術評論社 / ¥ 2,138 (2016-09-09)
 
発送可能時間:在庫あり。


2016/09/26


golang で簡単に DLL を呼び出す方法は syscall.NewLazyDLL を使う事です。

package main

var (
    times = 0
    dll   = syscall.NewLazyDLL("mydll.dll")
    proc  = dll.NewProc("MyFunc")
)

func main() {
    i := int32(123)
    proc.Call(uintptr(unsafe.Pointer(&i)))
}

ですが Call の引数は全て uintptr なので間違いが起きやすくなります。

※余談ですが cgo だと Go のポインタをC側に渡す事は出来ない(panicする)のですが、DLL の場合は許されています。

そこで golang には mksyscall_windows.go というツールが付属していいます。例えば Windows の API、FormatMessage を呼び出したいとします。

FormatMessage function (Windows)
https://msdn.microsoft.com/en-us/library/windows/desktop/ms679351(v=vs.85).aspx

MSDN のサイトを見ながら golang の型に合うよう、以下の様に記述します。この例ではファイル名は syscall_windows.go であるとします。

package main

//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go syscall_windows.go

//sys   FormatMessage(flags uint32, source syscall.Handle, messageID uint32, languageID uint32, buffer *byte, bufferSize uint32, arguments uintptr) (numChars uint32, err error) = kernel32.FormatMessageW

そしてコンソールから go generate を実行すれば以下の zsyscall_windows.go というファイルが生成されます。

// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT

package main

import (
    "syscall"
    "unsafe"

    "golang.org/x/sys/windows"
)

var _ unsafe.Pointer

// Do the interface allocations only once for common
// Errno values.
var (
    errERROR_IO_PENDING error = syscall.Errno(ERROR_IO_PENDING)
)

// errnoErr returns common boxed Errno values, to prevent
// allocations at runtime.
func errnoErr(e syscall.Errno) error {
    switch e {
    case 0:
        return nil
    case ERROR_IO_PENDING:
        return errERROR_IO_PENDING
    }
    // TODO: add more here, after collecting data on the common
    // error values see on Windows. (perhaps when running
    // all.bat?)
    return e
}

var (
    modkernel32 = windows.NewLazySystemDLL("kernel32.dll")

    procFormatMessageW = modkernel32.NewProc("FormatMessageW")
)

func FormatMessage(flags uint32, source syscall.Handle, messageID uint32, languageID uint32, buffer *byte, bufferSize uint32, arguments uintptr) (numChars uint32, err error) {
    r0, _, e1 := syscall.Syscall9(procFormatMessageW.Addr(), 7uintptr(flags), uintptr(source), uintptr(messageID), uintptr(languageID), uintptr(unsafe.Pointer(buffer)), uintptr(bufferSize), uintptr(arguments), 00)
    numChars = uint32(r0)
    if numChars == 0 {
        if e1 != 0 {
            err = errnoErr(e1)
        } else {
            err = syscall.EINVAL
        }
    }
    return
}

エディタや IDE からも補完できるので渡す引数の型が分かりやすくなります。

DLLの呼び出し

あとはプログラムから FormatMessage を呼び出すだけになりますが、Windows の API はプログラムを落とす事もできる危険な物もあります。internal パッケージを使って外部のパッケージからは見えない様にしておきましょう。

みんなのGo言語【現場で使える実践テクニック】 みんなのGo言語【現場で使える実践テクニック】
松木雅幸, mattn, 藤原俊一郎, 中島大一, 牧 大輔, 鈴木健太
技術評論社 / ¥ 2,138 (2016-09-09)
 
発送可能時間:在庫あり。


2016/09/21


以前から c-archive は作れたけど DLL にするとシンボルが被ったりして上手く DLL が作れなかった。

Big Sky :: golang の Windows 版が buildmode=c-archive をサポートした。

だいぶ時間が掛かった様ですが、ようやく buildmode=c-archive が Windows でも使える様になりました。 cmd/go: -buildmode=c-archive should ...

http://mattn.kaoriya.net/software/lang/go/20160405114638.htm
cmd/go: -buildmode=c-shared should work on windows · Issue #11058 · golang/go · GitHub
https://github.com/golang/go/issues/11058#issuecomment-248330515

-Wl,--allow-multiple-definition を知らなかった。

package main

import "C"

func main() {
}

//export HelloWorld
func HelloWorld() {
    println("Hello golang!")
}

まずこの様なソースコードを c-archive にして libxxx.a を作る。

go build -buildmode=c-archive -o libxxx.a xxx.go

次に HelloWorld をエントリにする def ファイルを作る。

LIBRARY   helloworld
EXPORTS
   HelloWorld

そして def ファイルを指定して DLL を生成する。この時 -Wl,--allow-multiple-definition を指定すること。

gcc -m64 -shared -o xxx.dll xxx.def libxxx.a -Wl,--allow-multiple-definition -static -lstdc++ -lwinmm -lntdll -lWs2_32

後は呼び出し元を作って...

package main

import "syscall"

var (
    lib        = syscall.NewLazyDLL("xxx.dll")
    helloworld = lib.NewProc("HelloWorld")
)

func main() {
    helloworld.Call()
}
Golang で DLL!

C言語からも

#include <windows.h>

typedef void (*pfn)(void);

int
main(int argc, char* argv[]) {
    pfn fn;
    HMODULE h = LoadLibrary("xxx.dll");
    fn = (pfn) GetProcAddress(h, "HelloWorld");
    fn();
    return 0;
}
Golang で DLL!

Golang のランタイムが FreeLibrary される事を想定していないので Vim の libcall からは直では呼び出せないけど libcallex-vim を使って解放なしに呼び出せば実行可能。ただしハンドルは持ちっぱなしにしないとリークする。

let xxx = libcallex#load("xxx.dll")
call xxx.call("HelloWorld", [], "")
"call xxx.free()

やったー!これで勝つる!

みんなのGo言語【現場で使える実践テクニック】 みんなのGo言語【現場で使える実践テクニック】
松木雅幸, mattn, 藤原俊一郎, 中島大一, 牧 大輔, 鈴木健太
技術評論社 / ¥ 2,138 (2016-09-09)
 
発送可能時間:在庫あり。