什么是系统调用
了解syscall包之前先了解下什么是系统调用。系统调用是程序向操作系统内核请求服务的过程,通常包含硬件相关的服务(例如访问硬盘),创建新进程等。系统调用提供了一个进程和操作系统之间的接口。
fmt中的syscall
最常见的关于syscall的使用是在fmt.Println中,具体代码的大家可以一步步往下看怎么调用的,这里使用了系统的syscall.Stdout
func Println(a ...interface{}) (n int, err error) {
return Fprintln(os.Stdout, a...)
}
Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
Go调用dll库
dll是windows动态库,go去调用动态库使用的是syscall标准库,一般dll库会提供两个固定函数,申请内存和释放内存,先申请完内存再执行业务逻辑的函数,执行完后释放内存。
dll, err := syscall.LoadDLL("scan.dll")
//根据名称从dll中查找proc
MemoryStream_Get = dll.FindProc("AllocateMemory")
MemoryStream_Get.Call()
主要就是三步:LoadDLL加载dll文件名,然后用FindProc判断查找调用的dll库函数名,然后Call进行调用。整体的调用方式还是比较简单。
LoadDLL会返回一个结构体DLL,注意Handle是一个uinptr类型(uintptr 是 Go 内置类型,表示无符号整数,可存储一个完整的地址,常用于指针运算),也就是会返回方法的地址值,后面的传参和解析通过结合只 unsafe.Pointer 类型转换成 uintptr 类型,做完加减法后,转换成 unsafe.Pointer,通过 * 操作,取值或者修改值都可以。
type DLL struct {
Name string
Handle Handle
}
调用dll如何传参
Proc的Call()方法是可接收多个uintptr的所以在传参的试试需要将参数转为uintptr
func (p *Proc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error) {
传递整型参数,将整型转为uintptr
func IntToPtr(n int) uintptr {
return uintptr(n)
}
传递字符串参数,将字符串转为uintptr,但是这里使用到了unsafe.Pointer,它是可以指向任意类型的指针,而syscall.StringBytePtr是将string转为 *btye指针。进而一步步转为uintptr
func StringToUintPtr(val string) uintptr {
return uintptr(unsafe.Pointer(syscall.StringBytePtr(val)))
}
接收dll库中返回值
读取调用库的返回值其实真正涉及到指针偏移的计算。因为uintptr指向的实际的整型地址值(申请内存方法会返回),我们可以根据返回的比如字符串在内存中长度来进行计算,比如长度是length(业务函数:GetDeviceInfo会返回),mem是实际调用dll库后返回的uintptr。因为调用dll库一般是返回char,所以这里转为byte即可。
type MemoryStream struct {
handle uintptr //空间地址值
lenHandle uintptr //数据长度
}
//获取设备信息 m.handle是申请内存的地址
func (m *MemoryStream) GetDeviceInfo() (result string) {
pd, _, _ := GetDeviceInfo.Call(m.handle)
m.lenHandle = pd
result = string(m.Bytes())
return result
}
//返回内存的数据内容byte[]
func (m *MemoryStream) Bytes() []byte {
buffer := new(bytes.Buffer)
length := m.Size()
mem := m.Memory()
if length == 0 {
return []byte{}
}
//根据长度,unsafe.Pointer进行指针运算
for i := int64(0); i < length; i++ {
buffer.WriteByte(*(*byte)(unsafe.Pointer(mem + uintptr(i)))) //byte是uint8, sizeof长度是1
}
return buffer.Bytes()
}
总结
syscall库支持对dll库的调用,当然它的功能很强大,可以实现很多我们没有接触过的业务场景。调用的方式比较清晰,但是设计到传参和解析返回值的时候需要用到unsafe.Pointer和uintpre之间的转换、dll库返回的char强制转换为byte,这一块有点逻辑转换。下次专门做个笔记记录下指针、uintptr、unsafe.Pointer之间的使用。