GoLang:致命错误:回调函数太多

GoLang:致命错误:回调函数太多,go,Go,我正在尝试编写一个go应用程序,它将监视我在windows中运行的服务器应用程序的状态。应用程序将运行大约16个小时,然后抛出以下错误:(小片段) 我有两个文件。一个是调用一堆Windows API的文件,另一个是每30秒执行一次的goroutine以获得更新 我是一个新手,尤其是在与windows相关的开发方面,所以我很难找到问题以及如何预防它 下面是主文件(测试示例) 这是主回调/windows文件。这仍然是一项非常粗糙、非常有进展的工作。我从围棋场找到并修改的东西 package main

我正在尝试编写一个go应用程序,它将监视我在windows中运行的服务器应用程序的状态。应用程序将运行大约16个小时,然后抛出以下错误:(小片段)

我有两个文件。一个是调用一堆Windows API的文件,另一个是每30秒执行一次的goroutine以获得更新

我是一个新手,尤其是在与windows相关的开发方面,所以我很难找到问题以及如何预防它

下面是主文件(测试示例)

这是主回调/windows文件。这仍然是一项非常粗糙、非常有进展的工作。我从围棋场找到并修改的东西

package main

import (
    "fmt"
    "syscall"
    "unsafe"
)

var (
    user32             = syscall.MustLoadDLL("user32.dll")
    procEnumWindows    = user32.MustFindProc("EnumWindows")
    procGetWindowTextW = user32.MustFindProc("GetWindowTextW")
    procIsHungAppWindow = user32.MustFindProc("IsHungAppWindow")
)

//EnumWindows iterates over each window to be used for callbacks
func EnumWindows(enumFunc uintptr, lparam uintptr) (err error) {
    r1, _, e1 := syscall.Syscall(procEnumWindows.Addr(), 2, uintptr(enumFunc), uintptr(lparam), 0)
    if r1 == 0 {
        if e1 != 0 {
            err = error(e1)
        } else {
            err = syscall.EINVAL
        }
    }
    return
}

//GetWindowText gets the description of the Window, which is the "Text" of the window.
func GetWindowText(hwnd syscall.Handle, str *uint16, maxCount int32) (len int32, err error) {
    r0, _, e1 := syscall.Syscall(procGetWindowTextW.Addr(), 3, uintptr(hwnd), uintptr(unsafe.Pointer(str)), uintptr(maxCount))
    len = int32(r0)
    if len == 0 {
        if e1 != 0 {
            err = error(e1)
        } else {
            err = syscall.EINVAL
        }
    }
    return
}

//IsHungAppWindow uses the IsHungAppWindow to see if Windows has been getting responses.
func IsHungAppWindow(hwnd syscall.Handle) (ishung bool, err error) {
    r2, _, err := syscall.Syscall(procIsHungAppWindow.Addr(), 2, uintptr(hwnd), 0, 0)
    if r2 == 1{
        return true, err
    } 
    return false, err
}

//FindWindow uses EnumWindows with a callback to GetWindowText, and if matches given Title, checks if its hung, and returns state.
func FindWindow(title string) (bool ,bool,  error) {
    var hwnd syscall.Handle
    var isHung bool = false
    cb := syscall.NewCallback(func(h syscall.Handle, p uintptr) uintptr {
        b := make([]uint16, 200)
        _, err := GetWindowText(h, &b[0], int32(len(b)))
        if err != nil {
            // ignore the error
            return 1 // continue enumeration
        }
        if syscall.UTF16ToString(b) == title {
            // note the window
            isHung, _ = IsHungAppWindow(h)

            hwnd = h

            return 0 // stop enumeration
        }
        return 1 // continue enumeration
    })

    EnumWindows(cb, 0)
    if hwnd == 0 {
        return false, false, fmt.Errorf("DCS Not Found")
    }
    
    if isHung == true {
        return true, isHung, fmt.Errorf("DCS Is Running But Hung")
    }

    return true, isHung, nil
}

//ServerHangCheck checks the server to see if the window is hung or process is running.
func ServerHangCheck() (bool, bool, error) {
    const title = "server_application"
    running, hung, err := FindWindow(title)

    return running, hung, err

}
查看一下,我们发现它实际上是通过
runtime/syscall\u windows.go
作为函数
compileCallback
实现的:

func NewCallback(fn interface{}) uintptr {
        return compileCallback(fn, true)
}
查看代码,我们发现它有一个包含所有已注册Go回调的固定大小的表。这段代码在Go版本之间变化很大,因此在这里进行深入研究不会太有成效。然而,有一点是明确的:代码检查回调函数是否已经注册,如果已经注册,则重新使用它。因此,一个回调函数占用一个表槽,但添加多个函数最终会占用所有表槽,并导致遇到的
致命错误

您在中询问(在我写这篇文章时弹出评论):


我需要重新使用它,还是可以“关闭”原始文件7分钟前的马拉喀尔

您不能关闭原始文件。内存中的其他地方有一个实际的函数表;注册的回调会占用此表中的一个插槽,并且插槽永远不会释放。

查看,我们发现它实际上是通过
运行时/syscall\u窗口实现的。go
作为函数
compileCallback

func NewCallback(fn interface{}) uintptr {
        return compileCallback(fn, true)
}
查看代码,我们发现它有一个包含所有已注册Go回调的固定大小的表。这段代码在Go版本之间变化很大,因此在这里进行深入研究不会太有成效。然而,有一点是明确的:代码检查回调函数是否已经注册,如果已经注册,则重新使用它。因此,一个回调函数占用一个表槽,但添加多个函数最终会占用所有表槽,并导致遇到的
致命错误

您在中询问(在我写这篇文章时弹出评论):


我需要重新使用它,还是可以“关闭”原始文件7分钟前的马拉喀尔


您不能关闭原始文件。内存中的其他地方有一个实际的函数表;注册的回调会占用此表中的一个插槽,并且插槽永远不会释放。

根据torek的建议,我找到了解决方法

我创建了一个全局变量

var callbacker uintptr 
然后我创建了一个由init调用的函数

func init() {

    callbacker = syscall.NewCallback(CallBackCreator)

}


func CallBackCreator(h syscall.Handle, p uintptr) uintptr {
    hwnd = 0
    b := make([]uint16, 200)
    _, err := GetWindowText(h, &b[0], int32(len(b)))
    if err != nil {
        // ignore the error
        return 1 // continue enumeration
    }
    if syscall.UTF16ToString(b) == title {
        // note the window
        isHung, _ = IsHungAppWindow(h)

        hwnd = h

        return 0 // stop enumeration
    }
    return 1 // continue enumeration
}
现在由函数调用

EnumWindows(callbacker, 0)

可能不是最适合“走”的方式,但我现在取得了更好的进展。

根据torek的建议,我找到了一个解决办法

我创建了一个全局变量

var callbacker uintptr 
然后我创建了一个由init调用的函数

func init() {

    callbacker = syscall.NewCallback(CallBackCreator)

}


func CallBackCreator(h syscall.Handle, p uintptr) uintptr {
    hwnd = 0
    b := make([]uint16, 200)
    _, err := GetWindowText(h, &b[0], int32(len(b)))
    if err != nil {
        // ignore the error
        return 1 // continue enumeration
    }
    if syscall.UTF16ToString(b) == title {
        // note the window
        isHung, _ = IsHungAppWindow(h)

        hwnd = h

        return 0 // stop enumeration
    }
    return 1 // continue enumeration
}
现在由函数调用

EnumWindows(callbacker, 0)

可能不是最适合“去”的方式,但我现在取得了更好的进展。

每次调用
FindWindow
时,您都在创建一个新的匿名函数。这意味着您每次都要创建一个新回调。最终,您会遇到创建了太多回调并导致程序崩溃的情况(如错误消息所示)。你需要找到一种方法来重新使用相同的回调我需要重新使用它,还是我可以“关闭”原始的?我不知道这是否有效,但你可以尝试这个函数是没有文档的,但这是我的第一个猜测。它接受类型为
Handle
的参数,该参数的基础类型为
uintptr
,因此您可以使用
syscall.Handle(cb)
进行转换。每次调用
FindWindow
时,您都会创建一个新的匿名函数。这意味着您每次都要创建一个新回调。最终,您会遇到创建了太多回调并导致程序崩溃的情况(如错误消息所示)。你需要找到一种方法来重新使用相同的回调我需要重新使用它,还是我可以“关闭”原始的?我不知道这是否有效,但你可以尝试这个函数是没有文档的,但这是我的第一个猜测。它接受类型为
Handle
的参数,该参数的基础类型为
uintptr
,因此您可以使用
syscall.Handle(cb)
进行转换。因此,我仍然不清楚如何纠正此问题。我有没有办法维持这个回调?或者我该如何补救?遗憾的是,我对Go和Windows开发都很陌生(大部分时间都在Linux上),我曾想过将HWND设置为全局变量,但如果服务器应用程序关闭并重新启动,那么我需要再次获取HWND。您需要调用
syscall.NewCallback
一次,以建立一些函数(获取一些参数集,并返回一个uintptr作为其结果)作为回调。例如:write
func windowCallBack(h syscall.Handle,p uintpttr)uintpttr{…}
(填写代码部分),然后进行一次调用,一次创建回调处理程序。在回调处理程序中,像往常一样使用
h
p
参数来执行您需要执行的任何操作。不过,我不熟悉WIndows本身,所以我不知道如何正确编写。我的猜测(但这只是猜测)这里的Windows回调可以传递一个指针,因此您可以创建一个包含两个变量的数据结构,并将其作为指针参数提供。但不需要了解更多有关Windows如何工作的信息(或访问正确的文档)这个猜测值不了多少钱。太好了,我会尝试一些东西。好吧,我按照你的建议做了,它成功了。