ほげほげブログ

TKの日常が語られる、誰向けかさっぱりわからないブログです。

GoでのDLL錬成術

こんにちはこんにちは!

Go言語狂の皆さん!!うっほ…いや、こんにちは!!!

きっと皆さんのことです。Goを書きすぎてGo言語脳になってしまった果てに、Windowsのアドインの作成とかでDLLを生成したくなったのでしょう!

そんな皆さんを救うべく、今日はDLLの錬成術について、簡単に書こうと思います。

それではうっほほほい!

もくじ

基本

DLL形式にコンパイルするためにはWindowsであればMinGWが必要です。

Go言語狂の皆さんですから、Go以外のコンパイラを入れることにためらいを覚えることは理解できます!でも入れてください!

入れましたか?それでは雛形を次に示します。

package main

import "C"

/*
  DLLエクスポートする関数が含まれているスクリプトの冒頭にはimport "C"を書いてください。約束です。
*/

/*
  コンパイラの都合でmain関数が必要です。形式だけでいいです。呼ばれることはありません。
*/

func main() {}


/*
出力する関数には出力する関数名を次のように詠唱することで指定してください。
 //とexportとの間にスペースを入れないでくださいね。これはコメントではありません。
*/

//export UhhoFunction
func UhhoFunction() {
   ...
}

そしたらコンパイルのお時間です。次のように実行してください。

go build -buildmode=c-shared  -o uhho.dll 

このようにすると、スクリプトコンパイルされ、uhho.dllが生成されます。幸せのウホときがやってきましたね!

構造体ポインタの引数

しばしば構造体へのポインタを引数とするようなDLLを要求されることがあります。安心してくだい。あなたの信じるGoはできる子です。次のように"unsafe"を使ってウホりましょう!

type UhoAttributes struct {
   Loudness int
   NumDrumming int
}

//export Drumming
func Drumming(pUho *uintptr) {
  var uho = *(*UhoAttributes)(unsafe.Pointer(pUho))
  for i := 0; i < uho.NumDrumming; i++ {
    for j := 0; j < uho.Loudness; j++{ 
      fmt.Printf("Uho")
    }
    fmt.Println()
  }
}

Easy Uhho

コールバック関数を呼ぶ

コールバック関数を引数で渡されることがあります。残念ながら、unsafeなどでfunc型変数にそのまま入れてもうまく行きません。

でも大丈夫です!Goにはsyscall.SyscallNという最強の呼び鈴があります(人によってはDLL呼び出しのときにも見ているかもしれませんね)。

こんな感じに使います!

type UhoFuncType *uintptr

func (u *UhoFuncType) Call(args ...uintptr) uintptr {
   r1, _, _ := syscall.SyscallN(uintptr(unsafe.Pointer(u)), args...)
   return r1
}

//export Drumming
func Drumming(uho *UhoFuncType) {
   uho.Call()
}

Fantastic Uhho

参考
syscall.SyscallNの3番目の戻り値は、GetLastErrorの結果です。必要に応じて活用してくださいね。

FreeLibraryの魔の手から逃れる

実はGo言語のDLLには弱点があります。

それは、親プログラムに見捨てられ、FreeLibraryされると落ちてしまう、というものです。もし皆さんがアドインを作成していて、何故かクラッシュするな、ということがあれば、それはこの仕様のせいかもしれません。

でも大丈夫!!!逃れる術があります。

Windowsでは、何故か次のようにモジュールハンドルを取ってきておくと、幾度FreeLibraryされてもUnloadされなくなるという謎な仕様 ^1 があります。

何故かは知りませんが提供されているこの仕様に、全力で便乗しましょう!

var _ unsafe.Pointer

var (
    modKernel32           = windows.NewLazySystemDLL("Kernel32.dll")
    procGetModuleHandleEx = modKernel32.NewProc("GetModuleHandleExW")
)

func GetModuleHandleEx(dwFlags uint32, lpModuleName string, phModule *HANDLE) error {
    str, _ := syscall.UTF16PtrFromString(lpModuleName)
    r0, _, err := syscall.SyscallN(procGetModuleHandleEx.Addr(), uintptr(dwFlags), uintptr(unsafe.Pointer(str)), uintptr(unsafe.Pointer(phModule)))
    if r0 != 0 {
        return err
    }
    return nil
}

type HANDLE uintptr

func init() {
    var nh HANDLE
    GetModuleHandleEx(1, "", &nh)
}

これでもう、FreeLibraryの恐怖に陥ることはありません。やったぜ。

まとめ

どうですか?ドラミングの目処は立ちましたか?

それでは!うっほほい!

Goで楽しくDLL叩き

こんにちはこんにちは

どうも、いつもはラノベの感想ばかり書き連ねているtkmax777です。

今日は久しぶりにGoのTipsを雑に書きます。

Go言語スキーな皆さんなら、きっとWin32APIを叩いたり、その他機器との接続などでDLLを叩きたいはず。

そこで、今回はGoのDLL呼び出し周りについてまとめます。

もくじ

基本

目的のDLLの関数を呼び出すための関数を、次のように生成することができます。

1: DLL呼び出しスクリプト生成をしてくれる最強プログラムの取得

go get golang.org/x/sys/windows/mkwinsyscall

2: おまじないを書いたGoファイル(ここではwinapi.go)を用意

package main

// NULLが使いたくなったとき用
// 別に0を直で書いても問題ない
const NULL = 0

/*
以下、呪文。-output以降は適宜ファイル名を改める必要がある。
以後、詠唱するときは、//の後に空白を入れてはならない。
*/
//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output winapi_generate.go winapi.go


/*
次のように
 //sys <任意の関数名>(<引数...>) (<戻り値>) = <DLL名>.<関数名>
を列挙する。
*/
//sys ShowCursor(state bool) (counter int) = user32.ShowCursor

3: 生成

go generate winapi.go

実際に生成されるものの例:

// Code generated by 'go generate'; 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.
const (
    errnoERROR_IO_PENDING = 997
)

var (
    errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
    errERROR_EINVAL     error = syscall.EINVAL
)

// errnoErr returns common boxed Errno values, to prevent
// allocations at runtime.
func errnoErr(e syscall.Errno) error {
    switch e {
    case 0:
        return errERROR_EINVAL
    case errnoERROR_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 (
    moduser32 = windows.NewLazySystemDLL("user32.dll")

    procShowCursor = moduser32.NewProc("ShowCursor")
)

func ShowCursor(state bool) (counter int) {
    var _p0 uint32
    if state {
        _p0 = 1
    }
    r0, _, _ := syscall.Syscall(procShowCursor.Addr(), 1, uintptr(_p0), 0, 0)
    counter = int(r0)
    return
}

4: 呼び出し

ハンドルを保持したりする場合はLockOSThreadを忘れずに。

func main() {
    // 今回は問題ないと思われるが、DLL呼び出しするときは付けておいたほうが無難
    runtime.LockOSThread()
    defer runtime.UnlockOSThread()

    ShowCursor(false)
}

Easy!!

GetLastError

Win32APIなりで良く見かけるGetLastError関数。残念ながらGoの仕様上同様の手続きで呼び出しても正常に動作しません。

代わりに次のように詠唱すると、GoのError変数の形でエラーを受けとることができます。

//sys ClipCursor(rect uintptr)(ok int, err error) [failretval==0] = user32.ClipCursor

戻り値にerr errorを追加し、その後に[failretval==0]を追記しました。

[failretval==0] が戻り値の値からエラーが発生していると判断する条件で、==0の場合は省略することができます。

これにより、エラーの判定条件に合致している場合にGetLastErrorの値がerrに代入されます。幸せですね。

引数の型

ここからは、Cの型とGoの型の対応が非自明な例について、簡単に紹介します。

構造体

定義

次のように、Cで定義されているのと同様の順番で構造体を定義する必要があります。

例: RECT

・ C

typedef struct tagRECT {
  LONG left;
  LONG top;
  LONG right;
  LONG bottom;
} RECT, *PRECT, *NPRECT, *LPRECT;

・ Go

type RECT struct {
    Left   int32
    Top    int32
    Right  int32
    Bottom int32
}

詠唱

先程のように、uintptr型を指定します。

//sys ClipCursor(rect uintptr)(ok int, err error) = user32.ClipCursor

呼び出し

次のように、先頭要素の番地を渡します。

var rect = RECT{...}

ClipCursor(uintptr(unsafe.Pointer(&rect.Left))

unsafeと仲良くなりましょう!!!

配列やスライス

詠唱

プリミティブな型なら、自動生成くんを信じてそのまま書きましょう

//sys Function(chars []byte) = dllname.Function

構造体とかの配列の場合は、先頭要素の番地を指定します。

//sys wtsEnumerateSessionsEx(hServer uintptr, pLevel *uint32, Filter uint32, ppSessionInfo uintptr, pCount *uint32) (ok bool) = Wtsapi32.WTSEnumerateSessionsExW

呼び出し

var chars = make([]byte, 100)
Function(chars)

var sessionInfo = make([]wts_SESSION_INFO_1, 1000)
wtsEnumerateSessionsEx(uintptr(hServer), pLevel, Filter, uintptr(unsafe.Pointer(&sessionInfo[0])), &pCount)

Float

意外な落とし穴はfloat32やfloat64だったりします。

そのままやろうとすると、uintptrに変換されてしまい、正しく渡されません。

詠唱

次のようにuint32やuint64で詠唱します。

//sys Function(number32 uint32, number64 uint64) = dllname.Function

呼び出し

次のように、float型をuint型に変換します。

var Number32 float32
var Number64 float64

Function(math.Float32bits(Number32), math.Float64bits(Number64))

関数ポインタ

コールバック関数などを渡すときは、次のように唱えると良いです。

詠唱

//sys Function(callback uintptr) = dllname.Function

呼び出し

重要事項
コールバック関数には戻り値が必ず必要です。もしvoidを戻り値とするような関数が求められてもreturn 0をしてください!

func callback() uintptr { ... }

~~~

Function(syscall.NewCallback(callback))

文字列の入出力

結構厄介です。

char[]の書き込み

詠唱

*byteとして詠唱します。

//sys Function(c *byte) = dllname.Function

呼び出し

var c = "abc"
p, err := syscall.BytePtrFromString(c)
if err != nil {
   ...
}

Function(p)

char[]の読み込み

詠唱

*byteとして詠唱します。

//sys Function() (pByte *byte) = dllname.Function

呼び出し

func UTF8PtrToString(p *byte) string {
    if p == nil {
        return ""
    }

    var char byte
    var chars = []byte{}

    for i := 0; ; i++ {
        char = *(*byte)(unsafe.Pointer(unsafe.Add(unsafe.Pointer(p), unsafe.Sizeof(byte(0))*uintptr(i))))
        // null char
        if char == 0 {
            break
        }
        chars = append(chars, char)
    }

    return string(chars)
}

func main() {
    var pByte = new(byte)
    Function(pByte)

    UTF8PtrToString(pByte)
}

UTF-16の書き込み

詠唱

[]uint16として詠唱します。

//sys Function(c []uint16) = dllname.Function

呼び出し

var c = "abc"
arr, err := syscall.UTF16FromString(c)
if err != nil {
   ...
}

Function(arr)

UTF-16ポインタの書き込み

詠唱

*uint16として詠唱します。

//sys Function(c *uint16) = dllname.Function

呼び出し

var c = "abc"
arr, err := syscall.UTF16PtrFromString(c)
if err != nil {
   ...
}

Function(arr)

UTF-16の 読み込み

詠唱

[]uint16として詠唱します。

//sys Function() (c []uint16) = dllname.Function

呼び出し

var c = Function(arr)
var cstring = syscall.UTF16ToString(c)

UTF-16ポインタの読み込み

詠唱

*uint16として詠唱します。

//sys Function(c *uint16) = dllname.Function

呼び出し

func UTF16PtrToString(p *uint16) string {
    if p == nil {
        return ""
    }

    var char uint16
    var chars = []uint16{}

    for i := 0; ; i++ {
        char = *(*uint16)(unsafe.Pointer(unsafe.Add(unsafe.Pointer(p), unsafe.Sizeof(uint16(0))*uintptr(i))))
        chars = append(chars, char)
        // null char
        if char == 0 {
            break
        }
    }
    return syscall.UTF16ToString(chars)
}

func main() {
    var pByte = new(byte)
    Function(pByte)

    UTF16PtrToString(pByte)
}

まとめ

これだけわかれば、大概困らないのではなかろうかという気がします。

これで今日から君も、Goマスターだ!!!!

MIDIパッドコントローラのドレミ【DTM】

こんにちはこんにちは

最近はもっぱら曲作りばかりで、お絵かきを久しくしていないkmc-id: tkmax777です。

この記事は何?

この記事はKMCアドベントカレンダー15日目の記事です。昨日の記事は、kmc-id: kypさんによる、「Discord+GCEでいい感じのゲームサーバーを建てる」でした。2年連続でkypさんの後に記事を書くことになっていますが、昨年に引き続きすごいですね。

kyp.jp

さて、それでは本題に入りましょう。

最近、こんなデバイスを勢いで買いました。

f:id:TKMAX777:20201214181422j:plain
Launchpad X

知る人ぞ知る、Novation Lanchpad Xです。いやはや、以前からPad演奏に憧れていて、ついに買ってしまいました。今回はこの機器について、少し書いていこうと思います。

目次

Padって何?

いきなりPadといわれても…という人もいるかもしれません。これが何なのか、まずは説明しましょう。

みなさんが音を入力する機器として、まず思い浮かぶのは何でしょうか。きっと、多くの人は、ピアノの鍵盤を考えるのではないでしょうか。

そうです。実際、一般的にはDTMをするとき、ピアノの鍵盤の形をした入力機器、通称、MIDIキーボードを用いてを用いて入力することが主流となっています。僕自身も普段はRolandのA-49を使っています。

MIDIパッドコントローラは、この鍵盤のドド♯レレ♯ミ…を正方形のボタンで整列させたもので、MIDIの入力機器の一種です。

配置

では実際に音の配置を模式図と使って見てみましょう。

音配置
どうですか?超直感的!って思う人もいれば、いや、何がなんだか…。というか、色付けされているのに、べつにドレミに対応していたりしないのかよ。とか思う人もいるのではないでしょうか。

僕は後者でした。色付けされているのにドレミに対応していない。これが厄介です。つまるところ、実際に演奏するためには、改めてドレミの配置をちゃんと覚えなければいけません。

ではどのように覚えるのか。僕も始めてまだ一ヶ月もたっていないものの、少しわかってきた気がするので、簡単にまとめることにします。

見方を変える

先程の図では、全ての音をならべました。しかし実際に重要なのは、明らかにドレミファソラシドの、計8個の音ですね。(なぜなら♯や♭が付いている音は、これらを基準にすれば即座にわかる)

というわけで、まずは先程の図から、他の音を省いてみましょう。

ドレミ

どうですか。少しはスッキリとしましたね。

最初、このパッドのことを、「ドド♯レレ♯‥」と並べたものだと紹介しました。しかし、少し考えてみてください。横向きに見るのは本当に得策でしょうか。

そうです。縦に見てみると、何とピアノでいうところの黒鍵と白鍵が、キレイに分離されているのです。まずは、このことに気がつくのがとても大切です。

つまり第一前提として、パッドは横ではなく、できるだけ縦にみましょう。

パターンを見る

ここまでで、この配列が決して乱雑さだけを生み出しているわけではないことがわかりました。

では続いて、実際どのように音の配置を覚えていけば良いのかを見ていきましょう。まずは、1オクターブ(ド〜シまでの1セット)を含むように、次のように一部を切り抜きます。

1オクターブ+α

もっとスッキリしましたね。なんと、実際には最低、この配置さえ覚えれば良いのです。徐々に道が見えてきませんか。

では、ここからわかることを見ていきましょう。

1オクターブの距離

次の図のように、1オクターブ上の音を出すためには、二つ右上の音を押すだけで良いことがわかります。簡単!

1オクターブ

ドからの相対位置

次の図のように、なんと良くみてみれば、ラ以外はドを基準にすれば、直感的に位置を捉えることができることがわかります。Excellent!

ドからの相対位置

ラの位置

つづいて、ラの位置を良く見てみましょう。

ラの位置

はい、そうです。青から白に変わるところに丁度ラがあります。よって、これも即座に見分けることができます!Awesome

全体を見る

ここまできたら、全体の図をもう一度見てみましょう。

全体

ここまでの知識を踏まえると、最初よりも秩序だって見えませんか。

そうです。見方さえわかってしまえば、煩雑ではなく、ちゃんと意味がある置き方がされていることがよくわかります。

移調

では、ここで移調のことを考えてみましょう。

例として、C♯(嬰ハ長調)を考えます。同様に、1オクターブ分をドレミ…に対応するキーに沿って抜き出すと、次のようになります。

C♯

ここで、先程C(ハ長調)の場合のキーも並べます。

C

どうですか?

そうです!配置が両者、全く変わりません。つまり、最終的に、ドレミの相対位置を覚えてしまえば、移調操作は手をその分ずらすだけで済むことがわかります。Amazing!

どうです?ピアノのような、調ごとに押さえ方がかわる楽器とは、おさらばしたくなってきませんか?

最後に

今回は、多くの人が見慣れていないであろう機器、MIDIパッドコントローラの使い方について、少しだけ見てみました。今まで多くの人が、仮に目にしたことがあっても難しそう…って諦めていたのではないでしょうか。この記事が、皆さんがPadを始める機会に少しでも貢献できれば嬉しい限りです。

それでは、明日の記事はkmc-id: naka6さんが担当予定です。お楽しみに!

宣伝

About KMC

KMC(京大マイコンクラブ)では、プログラミングをはじめ、その他DTMやお絵描きなど、PCをもちいた幅広い活動を日々行っています。興味があれば是非次をご覧ください。

www.kmc.gr.jp

About Advent Calender

冒頭でもお伝えしたとおり、この記事はKMCのアドベントカレンダーの15日目のものとなっています。是非併せて他の日の記事もご覧ください。

adventar.org

ココアさん