How to implement DllMain entry point in Go

huangapple go评论83阅读模式
英文:

How to implement DllMain entry point in Go

问题

我正在使用Go构建一个DLL。

我无法实现Windows的DllMain入口点。

我的目标是当应用程序通过LoadLibrary加载dll并调用Test方法时,DllMain也会被调用。然而,目前应用程序陷入了死锁状态,什么都没有发生。

这个应用程序是相当简单的Python代码

# App code
import ctypes
lib = ctypes.CDLL("./mydll.dll")
lib.Test()

注意:

  • 我正在使用以下版本进行构建

    go version go1.16.4 windows/amd64
    

    并使用以下命令进行构建

    go build -o mydll.dll --buildmode=c-shared
    
  • 如果我移除DllMain,一切都正常工作,应用程序可以成功调用Test方法。

  • 我知道我可以在DllMain下使用switch case来指定条件,比如进程附加、分离等,我只是想保持尽可能简单。

//Go Code
package main

import "C"

import (
	"fmt"
	"os"
)

//export Test
func Test() {
	os.Create("fooFile")
}


//export DllMain
func DllMain(a uintptr, b uint32, c uintptr) int32 {
	return 1
}

func main() {
}
英文:

I'm using Go to build a DLL.

I'm not able to implement windows DllMain entry point.

My goal is when an app loads the dll via LoadLibrary to call the Test method, the DllMain will be called as well. However currently the app is kind of stuck in deadlock and nothing happens.

The app is quite simple python code

# App code
import ctypes
lib = ctypes.CDLL("./mydll.dll")
lib.Test()

Notes:

  • I am building using

    go version go1.16.4 windows/amd64
    

    and building using

    go build -o mydll.dll --buildmode=c-shared
    
  • If I remove DllMain, everything works fine and app can call the Test method successfully.

  • I'm aware that I can switch case under DllMain to specify conditions such as process attached, detached ...etc, I just wanted to keep it as simple as possible.

//Go Code
package main

import "C"

import (
	"fmt"
	"os"
)

//export Test
func Test() {
	os.Create("fooFile")
}


//export DllMain
func DllMain(a uintptr, b uint32, c uintptr) int32 {
	return 1
}

func main() {
}

答案1

得分: 3

DllMain不能是Go函数,因为第一次调用Go函数会初始化Go运行时,而这不能在DllMain的范围内完成(在DllMain的范围内只能完成非常少的事情)。

作为一种解决方法,你可以用C语言编写DllMain,并在一个单独的线程中调用Go代码,就像这个示例中所示。但是在DllMain的范围内无法与该线程同步,这样做会导致死锁。

还有一个_cgo_wait_runtime_init_done函数,但它也是异步的。

因此,如果你需要在DLL加载时同步执行一些Go操作,那就没办法了。最好的方法是在调用任何其他API之前定义一个导出的“Init”函数并调用它。

当然,在Go中初始化加载的惯用方式是通过init()函数

package main

import "C"

import (
	"fmt"
)

func init() {
	fmt.Println("init()")
}

//export Test
func Test() {
	fmt.Println("Test()")
}

func main() {
}

编译并运行:

go build -o mydll.dll --buildmode=c-shared
rundll32 mydll,Test

输出:

init()
Test()
英文:

DllMain can't be a Go function, since invoking a Go function the first time initializes the Go runtime, which can't be done in scope of DllMain (there are very few things that can be done in scope of DllMain).

As a workaround, you can write DllMain in C and invoke Go code in a separate thread as in this example. But you can't synchronize with that thread in the scope of DllMain, doing so will again lead to a deadlock.

There's also _cgo_wait_runtime_init_done, but it also is asynchronous.

So if you need to perform some Go action synchronously on DLL attachment, you're out of luck. It's best to just define an "Init" exported function and call it before calling any other API.

And of course, the idiomatic way in Go to initialize on load is via an init() function:

package main

import "C"

import (
	"fmt"
)

func init() {
	fmt.Println("init()")
}

//export Test
func Test() {
	fmt.Println("Test()")
}

func main() {
}

Compile and run:

go build -o mydll.dll --buildmode=c-shared
rundll32 mydll,Test

Output:

<!-- language: lang-none -->

init()
Test()

huangapple
  • 本文由 发表于 2021年6月14日 19:28:09
  • 转载请务必保留本文链接:https://go.coder-hub.com/67969401.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定