syscall.Call に 構造体のポインタを渡す方法

ひょんなことから golang で dll を動的に呼び出さなくてならなくて、引数として構造体のポインタを渡さなくちゃならなかったんだけど、調べてもよくわからんかったのでメモしておく。

dll 呼び出し側は以下のようなC言語の実装になっているとする。

#include "stdio.h"

typedef struct struct1 {
    int x;
    int y;
    int ret;
    char name[8];
} struct1;

int add0(int x, int y)
{
    int ret;
    ret = x + y;
    return ret;
}

void add1(int x, int y, int *ret)
{
    *ret = x + y;
    return;
}

void add2(struct1 *args)
{
    args->ret = args->x + args->y;
    sprintf(args->name, "struct1");
    return;
}

golang から dll を呼び出すためにまず dll をLoadDLL でロードする必要がある。そのあと、関数を FindProcgolang の変数にバインドして Call メソッドでその関数を呼び出すんだが、渡す引数をすべて uintptr にしなければならない。Goの型を uintptr にするには変数のポインタを unsafe.Pointer で一度ラップしてからそれをさらに uintptr でラップすることでできる。ただ、配列や構造体は変数のポインタを渡してもちゃんと渡されなくて、第一要素のポインタを渡す必要があるようだ。

例えば var a [10]int のような場合は uintptr(unsafe.Pointer(&a)) ではなく uintptr(unsafe.Pointer(&a[0]) とする必要がある。構造体の場合の例では

var b struct {
  x int
  y int
}

このような構造体があったとしたら、uintptr(unsafe.Pointer(&b.x)) とすることでポインタが渡る。(ただ、複雑な構造体の場合は単純には渡せない可能性があり、その場合はどうするのかはまだ解明不足)

上記を踏まえたうえで、先の dll 内の関数を呼び出す golang のコードは以下になる。

package main

import (
    "C"
    "fmt"
    "log"
    "syscall"
)
import "unsafe"

type struct1 struct {
    x    int32
    y    int32
    ret  int32
    name [8]uint8
}

func main() {
    dll, err := syscall.LoadDLL("a.dll")
    if err != nil {
        log.Fatal("LoadDLL: ", err)
    }
    defer dll.Release()

    add0, err := dll.FindProc("add0")
    if err != nil {
        log.Fatal("FindProc(add2): ", err)
    }

    add1, err := dll.FindProc("add1")
    if err != nil {
        log.Fatal("FindProc(add2): ", err)
    }

    add2, err := dll.FindProc("add2")
    if err != nil {
        log.Fatal("FindProc(add2): ", err)
    }

    ret0, _, _ := add0.Call(1, 2)
    fmt.Println("add0:")
    fmt.Println(" ", ret0)

    var ret1 int
    add1.Call(1, 2, uintptr(unsafe.Pointer(&ret1)))
    fmt.Println("add1:")
    fmt.Println(" ", ret1)

    var ret2 struct1
    ret2.x = 1
    ret2.y = 2
    add2.Call(uintptr(unsafe.Pointer(&ret2.x)))
    fmt.Println("add2:")
    fmt.Println(" ", ret2.x)
    fmt.Println(" ", ret2.y)
    fmt.Println(" ", ret2.ret)
    fmt.Println(" ", string(ret2.name[:]))
}

以上