Search code examples
pythonwindowsgodlldllmain

How to implement DllMain entry point in Go


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() {
}

Solution

  • 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:

    init()
    Test()