使用syscall.SyscallN替代syscall.Syscall的Golang实践指南

使用syscall.SyscallN替代syscall.Syscall的Golang实践指南 鉴于Go文档的组织者,并希望我能帮助他人避免在了解如何访问Windows DLL上浪费数小时,本文尝试解释如何使用新的 syscall.SyscallN 功能来访问Windows上的自定义DLL(已在64位Win10上使用一个用C编写并用Visual Studio 2019编译的遗留64位DLL进行测试)。作为参考,截至2022年6月28日,SyscallN 的底层Go代码显然只在 https://github.com/golang/go/blob/master/src/syscall/dll_windows.gomkwinsyscall.go 源代码也可能有所帮助。

  1. 确保导入必要的Go模块(此示例假设使用 Go v1.18 +):

    import (
        . . .
        "syscall"
        "unsafe"
        "golang.org/x/sys/windows"
    )
    
  2. 加载DLL(如果未找到DLL,这将 引发Panic): 声明一个延迟加载的DLL引用和一个指向DLL中C函数过程地址的延迟指针:

    var pLazyDll *syscall.LazyDLL
    var pLazyProc *syscall.LazyProc
    

    然后获取DLL的实际引用(这假设您的DLL在64位Windows的Windows\System32目录下 - 如果不是,您需要查看Windows定位DLL的规则):

    pLazyDll = syscall.NewLazyDLL("name-of-dll-with-extension")
    

    例如: syscall.NewLazyDLL("myDLL.dll")

  3. 获取对过程的引用(如果未找到该过程,这将 引发Panic): pLazyProc = pLazyDll.NewProc(“name-of-proc-in-dll”) // 使用dumpbin或其他工具列出名称

  4. 定义一个 **Go** 函数来封装对DLL中 C 函数的调用

    您必须知道C函数(即DLL中的过程)所有参数的类型和返回类型! 举个例子,对于一个 **C** 函数:

    • 期望 一个 32int 和一个C字符缓冲区(const *char)作为参数(其中C函数将用NULL终止的C字符串填充缓冲区)
    • 返回 一个 32int 作为错误代码

    使用 syscall.SyscallN 调用DLL中C函数的 **Go** 函数包装器如下所示:

    func myGoFunction(p1 uint32, buf *byte) (err int32) {
        r0, _, _ := syscall.SyscallN(pLazyProc.Addr(), uintptr(p1), uintptr(unsafe.Pointer(buf)))
        err = int32(r0)
        return
    }
    

    注意 pLazyProc 结构的 Addr 成员包含被调用函数的地址,并且 所有 参数都必须转换为 uintptrr0 中的返回值必须转换为 int32 以匹配函数签名。有关 syscall.SyscallN 返回但此处未使用的值的更多信息,请参考上面链接中的源代码。

  5. 准备调用函数。由于在此示例中,**Go** 函数期望DLL过程返回一个(NULL终止的)C 字符串,调用者必须创建(分配)一个Go byte array 以传递给函数(连同 uint32 参数值):

    buffer := make([]byte, 256) // 缓冲区大小取决于可能返回的字符串大小
    var p1 uint32 = 42 // 示例中任意取值
    
  6. 调用函数并获取结果(如果缓冲区太小,这将 引发Panic):

    result := myGoFunction(p1, &buffer[0]) // 注意使用缓冲区第一个字节的地址

    返回的 result 将是一个 int32

    要从 C 缓冲区访问(NULL终止的)字符串,请使用Go windows 模块中的函数将其转换为 Go 字符串: goString := windows.BytePtrToString(&buffer[0])

就是这样。


更多关于使用syscall.SyscallN替代syscall.Syscall的Golang实践指南的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

由于编辑按钮神秘地消失了,请注意,在示例C函数的描述中,严格来说缓冲区参数应该是 *char,而不是 const *char,因为在DLL中内存会被修改(字符被放入缓冲区)。

更多关于使用syscall.SyscallN替代syscall.Syscall的Golang实践指南的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


// 补充一个完整的示例,展示如何使用syscall.SyscallN调用Windows API
package main

import (
    "fmt"
    "syscall"
    "unsafe"
    "golang.org/x/sys/windows"
)

var (
    kernel32         = syscall.NewLazyDLL("kernel32.dll")
    procGetTempPathW = kernel32.NewProc("GetTempPathW")
)

// 封装GetTempPathW API
func GetTempPath() (string, error) {
    var buffer [windows.MAX_PATH]uint16
    
    // 使用SyscallN调用,注意参数转换
    r0, _, e1 := syscall.SyscallN(
        procGetTempPathW.Addr(),
        uintptr(windows.MAX_PATH),
        uintptr(unsafe.Pointer(&buffer[0])),
    )
    
    if r0 == 0 {
        return "", e1
    }
    
    return windows.UTF16ToString(buffer[:r0]), nil
}

// 示例:调用自定义DLL函数
var (
    customDll     *syscall.LazyDLL
    customProc    *syscall.LazyProc
)

func init() {
    customDll = syscall.NewLazyDLL("custom.dll")
    customProc = customDll.NewProc("CalculateSum")
}

// 假设DLL函数签名:int CalculateSum(int a, int b)
func CalculateSum(a, b int32) int32 {
    r0, _, _ := syscall.SyscallN(
        customProc.Addr(),
        uintptr(a),
        uintptr(b),
    )
    return int32(r0)
}

// 处理字符串参数的示例
func ProcessString(input string) (string, error) {
    proc := customDll.NewProc("ProcessStringW")
    
    // 转换Go字符串为UTF-16指针
    inputPtr, err := windows.UTF16PtrFromString(input)
    if err != nil {
        return "", err
    }
    
    var outputBuffer [256]uint16
    
    r0, _, e1 := syscall.SyscallN(
        proc.Addr(),
        uintptr(unsafe.Pointer(inputPtr)),
        uintptr(unsafe.Pointer(&outputBuffer[0])),
        uintptr(len(outputBuffer)),
    )
    
    if r0 == 0 {
        return "", e1
    }
    
    return windows.UTF16ToString(outputBuffer[:]), nil
}

func main() {
    // 示例1:调用系统API
    tempPath, err := GetTempPath()
    if err != nil {
        fmt.Printf("获取临时路径失败: %v\n", err)
    } else {
        fmt.Printf("临时路径: %s\n", tempPath)
    }
    
    // 示例2:调用自定义DLL
    sum := CalculateSum(10, 20)
    fmt.Printf("计算结果: %d\n", sum)
    
    // 示例3:处理字符串
    result, err := ProcessString("test input")
    if err != nil {
        fmt.Printf("处理字符串失败: %v\n", err)
    } else {
        fmt.Printf("处理结果: %s\n", result)
    }
}
// 处理不同参数类型的示例
func CallWithVariedParams() {
    proc := customDll.NewProc("VariedParamsFunc")
    
    // 不同类型参数的转换示例
    intParam := int32(100)
    uintParam := uint32(200)
    boolParam := true
    floatParam := float32(3.14)
    stringParam := "test"
    
    // 字符串转换为指针
    strPtr, _ := windows.UTF16PtrFromString(stringParam)
    
    // 结构体参数示例
    type MyStruct struct {
        Field1 int32
        Field2 uintptr
    }
    structParam := &MyStruct{Field1: 1, Field2: 2}
    
    // 使用SyscallN调用
    r0, _, _ := syscall.SyscallN(
        proc.Addr(),
        uintptr(intParam),           // int32
        uintptr(uintParam),          // uint32
        uintptr(boolToUintptr(boolParam)), // bool
        uintptr(*(*uint32)(unsafe.Pointer(&floatParam))), // float32
        uintptr(unsafe.Pointer(strPtr)),    // string
        uintptr(unsafe.Pointer(structParam)), // struct
    )
    
    fmt.Printf("返回值: %d\n", int32(r0))
}

func boolToUintptr(b bool) uintptr {
    if b {
        return 1
    }
    return 0
}
// 处理回调函数的示例
func CallWithCallback() {
    proc := customDll.NewProc("RegisterCallback")
    
    // 定义回调函数
    callback := syscall.NewCallback(func(param uintptr) uintptr {
        fmt.Printf("回调被调用,参数: %d\n", param)
        return param + 1
    })
    
    // 调用DLL函数,传递回调
    r0, _, _ := syscall.SyscallN(
        proc.Addr(),
        callback,
    )
    
    fmt.Printf("注册回调结果: %d\n", int32(r0))
}
// 错误处理的最佳实践
func SafeDllCall() (result int32, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("DLL调用panic: %v", r)
        }
    }()
    
    proc := customDll.NewProc("SomeFunction")
    
    // 检查过程是否有效
    if proc == nil {
        return 0, fmt.Errorf("未找到过程")
    }
    
    r0, _, e1 := syscall.SyscallN(proc.Addr())
    
    // 检查Windows错误
    if e1 != nil && e1.(syscall.Errno) != 0 {
        return int32(r0), e1
    }
    
    return int32(r0), nil
}
回到顶部