概要
いつの間にか-buildmode=c-shared
でWindowsのDLLが作成できるようになっていたのでサンプルを作って試してみた。
環境
- Windows 10
- go 1.12.5
サンプル
DLLのソース
package main
import "C"
//export afunc
func afunc() int {
return 1234
}
func main(){
}
import "C"
は必須で公開したい関数を//export 関数名
で指定します。
DLLをビルドします。
go build -buildmode=c-shared -o goshared.dll goshared.go
このときgoshared.dll
とgoshared.h
が作成されます。goshared.h
でafunc
がexportされています。以下は抜粋です。
#ifdef __cplusplus
extern "C" {
#endif
extern GoInt afunc();
#ifdef __cplusplus
}
#endif
テスト用のEXEソース
package main
import (
"fmt"
"syscall"
)
var (
dll = syscall.NewLazyDLL("goshared.dll")
proc = dll.NewProc("afunc")
)
func main() {
ret, _, _ := proc.Call()
fmt.Println(ret)
}
実行
go run main.go
1234
DllMainについて
WindowsのDLLはDllMain
があればDLLをロードしたときにDllMainを呼び出します。 DllMainをexportとしてみます
package main
import "C"
var num = 12345
//export afunc
func afunc() int {
return num
}
//export DllMain
func DllMain(arg1,arg2,arg3,arg4 uintptr) int{
num = 9876
}
func main(){
}
これをビルドして実行したところexeはフリーズした状態になりました。CTRL+C
でも終了させられなかったのでタスクマネジャーで強制終了させました。
ネットで調べたら https://github.com/NaniteFactory/dllmain を見つけました。
ここのdllmain.c
の31行目に次ようなコメントがありました。
// CreateThread() because otherwise DllMain() is highly likely to deadlock.
なのでこのソースでは別スレッドにしてGoの関数を呼び出すようにしています。
このサイトのソースをダウンロードして試してみました。
package main
import "C"
import "unsafe"
var num int = 1234
//export afunc
func afunc() int {
return num
}
//export OnProcessAttach
func OnProcessAttach(
hinstDLL unsafe.Pointer, // handle to DLL module
fdwReason uint32, // reason for calling function
lpReserved unsafe.Pointer, // reserved
) {
num = 456
}
func main(){
}
ダウンロードしたdllmain.go
は次のようになっていますが
package main
//#include "dllmain.h"
import "C"
ビルドは出来ましたが、自分の環境では //#include "dllmain.c"
でないとOnProcessAttach
が実行していないようでした。
ビルドします。
go build -buildmode=c-shared -o goshared.dll goshared.go dllmain.go
EXEの実行
go run main.go
1234
OnProcessAttach
が最初に実行されていれば456
が表示されているはずですが、DllMain
から別スレッドを作成してから呼び出されているので実行が遅れていると思われます。
これを確かめるため、main.go
を次のようにします。
package main
import (
"fmt"
"syscall"
)
var (
dll = syscall.NewLazyDLL("goshared.dll")
proc = dll.NewProc("afunc")
)
func main() {
ret, _, _ := proc.Call()
fmt.Println(ret)
var input string
fmt.Print("enter> ")
fmt.Scanf("%s", &input)
ret, _, _ = proc.Call()
fmt.Println(ret)
}
実行します。
1234
enter>
456
やはり、実行が遅れています。また、num
は別スレッドで変更しているので、本来はロックが必要でしょう。
init関数
package main
import "C"
var num int = 1234
//export afunc
func afunc() int {
return num
}
func init() {
num = 9876
}
func main(){
}
実行します。
go run main.go
1234
enter>
9876
init
関数もDllMain
の実験と同じように遅れるようです。
Go言語のDLLからDLLのLoad
自分の環境ではafunc
からsyscall.LoadDLL
を実行したらpanic
が起きました。解決策は不明のままです。解決できた方は投稿をお願いします。
fatal error: unexpected signal during runtime execution
[signal 0xc0000005 code=0x1 addr=0x0 pc=0x62ecdad8]
runtime stack:
runtime.throw(0x62f042a9, 0x2a)
C:/Go/src/runtime/panic.go:617 +0x79
runtime.sigpanic()
C:/Go/src/runtime/signal_windows.go:227 +0x272
runtime.memmove(0x0, 0xc000069b90, 0x1)
C:/Go/src/runtime/memmove_amd64.s:146 +0x108
runtime.heapBitsSetType(0xc000069b90, 0x30, 0x30, 0x62ef9620)
C:/Go/src/runtime/mbitmap.go:1400 +0x4ae
runtime.mallocgc(0x30, 0x62ef9620, 0x1, 0x7ff9bdde83d0)
C:/Go/src/runtime/malloc.go:969 +0x55b
runtime.newdefer.func2()
C:/Go/src/runtime/panic.go:242 +0xaa
runtime.systemstack(0x6fff10)
C:/Go/src/runtime/asm_amd64.s:351 +0x6b
runtime.mstart()
C:/Go/src/runtime/proc.go:1153
goroutine 1 [running, locked to thread]:
runtime.systemstack_switch()
C:/Go/src/runtime/asm_amd64.s:311 fp=0xc00004daa8 sp=0xc00004daa0 pc=0x62eca6f0
runtime.newdefer(0x0, 0x0)
C:/Go/src/runtime/panic.go:240 +0x183 fp=0xc00004db18 sp=0xc00004daa8 pc=0x62ea6b53
runtime.deferproc(0x0, 0x62f05380)
C:/Go/src/runtime/panic.go:107 +0x45 fp=0xc00004db48 sp=0xc00004db18 pc=0x62ea6605
syscall.loadlibrary(0xc0000720e0, 0x0, 0x0)
C:/Go/src/runtime/syscall_windows.go:145 +0x7f fp=0xc00004db78 sp=0xc00004db48 pc=0x62ebe34f
syscall.LoadDLL(0x62efff55, 0xc, 0xc000046000, 0xc000070000, 0xc00004dcd0)
C:/Go/src/syscall/dll_windows.go:82 +0x3b6 fp=0xc00004dc30 sp=0xc00004db78 pc=0x62ed3d56
main.afunc(0x0)
C:/ 内緒 /goshared.go:18 +0x4a fp=0xc00004dc88 sp=0xc00004dc30 pc=0x62eda36a
main._cgoexpwrap_14c397c31a8d_afunc(0xc000074010)
_cgo_gotypes.go:45 +0x29 fp=0xc00004dca0 sp=0xc00004dc88 pc=0x62eda2f9
runtime.call32(0x0, 0x6ffcb0, 0x6ffe28, 0x8)
C:/Go/src/runtime/asm_amd64.s:519 +0x42 fp=0xc00004dcd0 sp=0xc00004dca0 pc=0x62ecaab2
runtime.cgocallbackg1(0x0)
C:/Go/src/runtime/cgocall.go:314 +0x185 fp=0xc00004dd48 sp=0xc00004dcd0 pc=0x62e83e15
runtime.cgocallbackg(0x0)
C:/Go/src/runtime/cgocall.go:191 +0xda fp=0xc00004ddb8 sp=0xc00004dd48 pc=0x62e83bda
runtime: unexpected return pc for runtime.cgocallback_gofunc called from 0x44b3f7
まとめ
簡単にWindows DLLができるようになったのは良かったですがDllMain
,init
は使わないで初期化関数を定義して最初に呼び出したほうが良さそうです。
また、syscall.LoadDLL
でpanic
が起きることも含め、このようなGo言語DLLを使うことはちょっと不安になります。
Appendix
dumpbin /exports goshared.dll の抜粋
19番目にafunc
が載っています。最初の方に出るように先頭をa
にしました。また48番目にinit
,54番目にnum
があります。全部で1595個ありました。多すぎる。
ordinal hint RVA name
1 0 00058980 _cgo_get_context_function = ___w64_mingwthr_remove_key_dtor
2 1 0005BB28 _cgo_init
3 2 00058820 _cgo_is_runtime_initialized
4 3 00058760 _cgo_maybe_run_preinit
5 4 0005BB30 _cgo_notify_runtime_init_done
6 5 00052350 _cgo_panic
7 6 00058700 _cgo_preinit_init
8 7 000586D0 _cgo_release_context
9 8 0005BB38 _cgo_sys_thread_create
10 9 00058A70 _cgo_sys_thread_start
11 A 0005BB40 _cgo_thread_start
12 B 0004C2D0 _cgo_topofstack
13 C 00058850 _cgo_wait_runtime_init_done
14 D 000D20D0 _cgo_yield
15 E 00058520 _cgoexp_a74ddd773b6e_afunc
16 F 0004DAE0 _rt0_amd64_windows_lib
17 10 0005BB08 _rt0_amd64_windows_lib.ptr
18 11 0004DB40 _rt0_amd64_windows_lib_go
19 12 00058690 afunc
20 13 000523A0 crosscall2
21 14 00058AC0 crosscall_amd64
22 15 000013C0 go.buildid
23 16 0008D860 go.itab.*syscall.DLLError,error
24 17 0008D880 go.itab.runtime.errorString,error
25 18 0008D8A0 go.itab.syscall.Errno,error
26 19 000028A0 internal/bytealg.IndexByteString
27 1A 000EEB90 internal/bytealg.MaxLen
28 1B 00002430 internal/bytealg.init
29 1C 00002400 internal/bytealg.init.0
30 1D 000EEB20 internal/bytealg.initdone·
31 1E 000EF000 internal/cpu.ARM64
32 1F 0005B038 internal/cpu.CacheLineSize
33 20 00001440 internal/cpu.Initialize
34 21 000EEF60 internal/cpu.X86
35 22 00001F90 internal/cpu.cpuid
36 23 00001B80 internal/cpu.doinit
37 24 00001B40 internal/cpu.indexByte
38 25 000D5660 internal/cpu.options
39 26 000014A0 internal/cpu.processOptions
40 27 0008E220 internal/cpu.statictmp_0
41 28 00001FB0 internal/cpu.xgetbv
42 29 000524D0 internal/syscall/windows/sysdll.Add
43 2A 000D5020 internal/syscall/windows/sysdll.IsSystemDLL
44 2B 000525C0 internal/syscall/windows/sysdll.init
45 2C 00052560 internal/syscall/windows/sysdll.init.ializers
46 2D 000EEB21 internal/syscall/windows/sysdll.initdone·
47 2E 00058580 main._cgoexpwrap_a74ddd773b6e_afunc
48 2F 00058620 main.init
49 30 00058590 main.init.0
50 31 000585B0 main.init.1
51 32 000EEB22 main.initdone·
52 33 0005BC50 main.keepalive_modinfo
53 34 000585A0 main.main
54 35 0005B040 main.num