用go来写.
step.1 测量一个对象的大小
import “unsafe”
var uno uintptr // 这个类型是存放proc-address的
fmt.Printf(“%v\n”, unsafe.Sizeof(uno))
step.2 加载我们的handler
1) 采用syscall.LoadLibrary syscall.GetProcAddr syscall.FreeLibrary
我们的入口不在kernel32.dll上面, 所以若取得proc地址之后却FreeLibrary, 那么syscall.Syscall6对其调用会失败.
屏幕上打印出一堆地址和寄存器, 没有报告到底是什么错误. (只需要把LoadLibrary后面defer FreeLibrary去掉即可调用成功)
2) 还是采用MustLoadDLL然后用sys.DLL的方法MustFindProc 就不会有Free的烦恼.
调用的话参数还异常简单
step.3 内存布局 从C struct 到golang struct 从我们的EnumPorts来看没有任何对齐的问题. (全是64bit一个域)
(问题正好在这里。 查了半天, 发现我们应该保证在golang中以相同的方式遍历buffer, 第一个前提就是struct的尺寸大小的一致! golang都是64中运行的. 用file查看go build之后的. 想必go run的运行时也是x64…懒 就不看了.)
unsafe.Sizeof一下一个struct对比大小。 这个正确了, 几本就找到解了.
step.4 显示字符串. syscall.UTF16ToString 需要一个[]uint16的slice
这个没问题 比如有 ptr *uint16 (先转array再转slice, 后者会产生一个slice头部的拷贝)
那么 (*[1<<30]uint16)(unsafe.Pointer(ptr))[:]
syscall.UTF16ToString([]uint16)string
package main
import (
"fmt"
"reflect"
"syscall"
"unsafe"
)
var (
nEnumPortsW *syscall.Proc
)
/* definition in C
typedef struct _PORT_INFO_2 {
LPTSTR pPortName;
LPTSTR pMonitorName;
LPTSTR pDescription;
DWORD fPortType;
DWORD Reserved;
} PORT_INFO_2, *PPORT_INFO_2;
*/
// This is x64
// If you need to test against a C version, be sure to make it x64
type PortInfo2 struct {
pPortName *uint16
pMonitorName *uint16
pDescription *uint16
fPortType uint32
reserved uint32
}
func init() {
wp := syscall.MustLoadDLL("Winspool.drv")
nEnumPortsW = wp.MustFindProc("EnumPortsW")
//var pi PortInfo2
//fmt.Printf("Size of obj is: %v\n", unsafe.Sizeof(pi))
}
func toStr(str *uint16) string {
strSlice := (*[1 << 30]uint16)(unsafe.Pointer(str))[:]
return syscall.UTF16ToString(strSlice)
}
func main() {
var cbNeeded, cReturned uint32
nEnumPortsW.Call(
0, // null
2, // level
0, // return buffer.
uintptr(cbNeeded), // 0.
uintptr(unsafe.Pointer(&cbNeeded)),
uintptr(unsafe.Pointer(&cReturned)),
)
//nEnumPorts(NULL, 2, (LPBYTE)pPort, pcbNeeded, &pcbNeeded, &pcReturned);
// fmt.Printf("cb: %v\n", cbNeeded)
// fmt.Printf("cReturned: %v\n", cReturned)
buffer := make([]byte, cbNeeded)
nEnumPortsW.Call(
0,
2,
uintptr(unsafe.Pointer(&buffer[0])),
uintptr(cbNeeded), //now we have
uintptr(unsafe.Pointer(&cbNeeded)),
uintptr(unsafe.Pointer(&cReturned)),
)
hdr := reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(&buffer[0])),
Len: int(cReturned),
Cap: int(cReturned),
}
pInfos := *(*[]PortInfo2)(unsafe.Pointer(&hdr))
pInfos = pInfos[:] // you can even copy all.
for _, t := range pInfos {
// And the length of array is 1<<30. Pretty sure unsafe.
// First make the pointer an array (with length of inifnite)
// Then make the array into an slice ( cast with `[:]' )
fmt.Printf("%v - %v - %v\n", toStr(t.pPortName),
toStr(t.pMonitorName),
toStr(t.pDescription),
)
}
}
参考链接:
https://stackoverflow.com/questions/38509347/sized-data-load-in-golang-getting-uint16-into-a-uint8-slice
https://stackoverflow.com/questions/28886616/convert-array-to-slice-in-go
https://talks.golang.org/2012/10things.slide#15
https://hacpai.com/article/1446417162636?m=0
https://github.com/golang/go/wiki/WindowsDLLs
最重要的是下面这个链接:
https://stackoverflow.com/questions/33769766/use-structs-with-golang-syscall-on-windows
注意, 使用连续内存时应该跳过头部。 也就是&buffer和&buffer[0]是不同的.
package main
import (
"fmt"
)
func main(){
buffer:=make([]byte,1024)
fmt.Printf("&buffer: %p\n", &buffer)
fmt.Printf("&buffer[0]: %p\n", &buffer[0])
}