使用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.go。
mkwinsyscall.go 源代码也可能有所帮助。
-
确保导入必要的Go模块(此示例假设使用 Go v1.18 +):
import ( . . . "syscall" "unsafe" "golang.org/x/sys/windows" ) -
加载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") -
获取对过程的引用(如果未找到该过程,这将 引发Panic):
pLazyProc = pLazyDll.NewProc(“name-of-proc-in-dll”) // 使用dumpbin或其他工具列出名称 -
定义一个
**Go**函数来封装对DLL中 C 函数的调用您必须知道C函数(即DLL中的过程)所有参数的类型和返回类型! 举个例子,对于一个
**C**函数:- 期望 一个 32 位
int和一个C字符缓冲区(const *char)作为参数(其中C函数将用NULL终止的C字符串填充缓冲区) - 返回 一个 32 位
int作为错误代码
使用
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 成员包含被调用函数的地址,并且 所有 参数都必须转换为uintptr。r0中的返回值必须转换为int32以匹配函数签名。有关syscall.SyscallN返回但此处未使用的值的更多信息,请参考上面链接中的源代码。 - 期望 一个 32 位
-
准备调用函数。由于在此示例中,
**Go**函数期望DLL过程返回一个(NULL终止的)C 字符串,调用者必须创建(分配)一个Gobyte array以传递给函数(连同uint32参数值):buffer := make([]byte, 256) // 缓冲区大小取决于可能返回的字符串大小 var p1 uint32 = 42 // 示例中任意取值 -
调用函数并获取结果(如果缓冲区太小,这将 引发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
由于编辑按钮神秘地消失了,请注意,在示例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
}

