go 调用windows api

go调用windows api分为两种方式,一种是懒加载(第一次调用函数时才加载dll),一种是立即加载。

需要注意的是,windows api里面有很多常量和结构体需要自己转化成go的才可以用(文档中有对应的说明),如下获取系统进程列表时需要用到的部分其实都是根据官方文档转化而来。windows api 链接地址

需要特别注意的是,API函数的参数都是uintptr类型,对指针类型需要通过unsafe.Pointer函数来转换,如果是false和null就用0代替

不再需要使用DLL里的函数之后可以卸载DLL,可使用syscall.FreeLibrary来卸载。

1、懒加载:kernel32 =syscall.NewLazyDLL(“kernel32.dll”)

package main

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

//todo windows api 相关
type (
BOOL uint32
BOOLEAN byte
BYTE byte
DWORD uint32
DWORD64 uint64
HANDLE uintptr
HLOCAL uintptr
LARGE_INTEGER int64
LONG int32
LPVOID uintptr
SIZE_T uintptr
UINT uint32
ULONG_PTR uintptr
ULONGLONG uint64
WORD uint16
)

type PROCESSENTRY32 struct {
dwSize DWORD
cntUsage DWORD
th32ProcessID DWORD
th32DefaultHeapID ULONG_PTR
th32ModuleID DWORD
cntThreads DWORD
th32ParentProcessID DWORD
pcPriClassBase LONG
dwFlags DWORD
szExeFile [260]byte
}

const (
//Tool Help Library tlhelp.h
//CreateToolhelp32Snapshot==>可以通过获取进程信息为指定的进程、进程使用的堆[HEAP]、模块[MODULE]、线程建立一个快照。说到底,可以获取系统中正在运行的进程信息,线程信息,等。
TH32CS_INHERIT = 0x80000000 //声明快照句柄是可继承的
TH32CS_SNAPHEAPLIST = 0x00000001 //在快照中包含在th32ProcessID中指定的进程的所有的堆
TH32CS_SNAPMODULE = 0x00000008 //在快照中包含在th32ProcessID中指定的进程的所有的模块
TH32CS_SNAPMODULE32 = 0x00000010 //包括所有的 32 位模块的 th32ProcessID 在快照时从 64 位进程调用中指定的进程
TH32CS_SNAPPROCESS = 0x00000002 //在快照中包含系统中所有的进程
TH32CS_SNAPTHREAD = 0x00000004 //在快照中包含系统中所有的线程
TH32CS_SNAPALL = (TH32CS_SNAPHEAPLIST | TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD | TH32CS_SNAPMODULE) //在快照中包含系统中所有的进程和线程。
)

func main() {
//懒加载获取进程列表==>懒加载
Kernel32 := syscall.NewLazyDLL("Kernel32.dll")
//获取快照
CreateToolhelp32Snapshot := Kernel32.NewProc("CreateToolhelp32Snapshot")
//调用函数
phandle, _, _ := CreateToolhelp32Snapshot.Call(uintptr(TH32CS_SNAPPROCESS), uintptr(0))
//延迟关闭快照句柄
defer func() {
CloseHandle := Kernel32.NewProc("CloseHandle")
_, _, _ = CloseHandle.Call(phandle)
}()

Process32Next := Kernel32.NewProc("Process32Next")
for {
var pe32 PROCESSENTRY32
pe32.dwSize = DWORD(unsafe.Sizeof(pe32))

rt, _, _ := Process32Next.Call(uintptr(phandle), uintptr(unsafe.Pointer(&pe32)))
if int(rt) == 1 {
fmt.Println("进程pid:",pe32.th32ProcessID,"进程名称:",string(pe32.szExeFile[0:]),"线程数量:",pe32.cntThreads)
} else {
break
}
}
}

2、立即加载:kernel32,_ = syscall.LoadLibrary(“kernel32.dll”)

package main

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

//todo windows api 相关
type (
BOOL uint32
BOOLEAN byte
BYTE byte
DWORD uint32
DWORD64 uint64
HANDLE uintptr
HLOCAL uintptr
LARGE_INTEGER int64
LONG int32
LPVOID uintptr
SIZE_T uintptr
UINT uint32
ULONG_PTR uintptr
ULONGLONG uint64
WORD uint16
)

type PROCESSENTRY32 struct {
dwSize DWORD
cntUsage DWORD
th32ProcessID DWORD
th32DefaultHeapID ULONG_PTR
th32ModuleID DWORD
cntThreads DWORD
th32ParentProcessID DWORD
pcPriClassBase LONG
dwFlags DWORD
szExeFile [260]byte
}

const (
//Tool Help Library tlhelp.h
//CreateToolhelp32Snapshot==>可以通过获取进程信息为指定的进程、进程使用的堆[HEAP]、模块[MODULE]、线程建立一个快照。说到底,可以获取系统中正在运行的进程信息,线程信息,等。
TH32CS_INHERIT = 0x80000000 //声明快照句柄是可继承的
TH32CS_SNAPHEAPLIST = 0x00000001 //在快照中包含在th32ProcessID中指定的进程的所有的堆
TH32CS_SNAPMODULE = 0x00000008 //在快照中包含在th32ProcessID中指定的进程的所有的模块
TH32CS_SNAPMODULE32 = 0x00000010 //包括所有的 32 位模块的 th32ProcessID 在快照时从 64 位进程调用中指定的进程
TH32CS_SNAPPROCESS = 0x00000002 //在快照中包含系统中所有的进程
TH32CS_SNAPTHREAD = 0x00000004 //在快照中包含系统中所有的线程
TH32CS_SNAPALL = (TH32CS_SNAPHEAPLIST | TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD | TH32CS_SNAPMODULE) //在快照中包含系统中所有的进程和线程。
)

func main() {
//获取进程列表==>立即加载
kernel32,_:=syscall.LoadLibrary("kernel32")
//调用函数
CreateToolhelp32Snapshot, _ :=syscall.GetProcAddress(kernel32,"CreateToolhelp32Snapshot")
//其他参数补0就行了
pHandle,_,_:=syscall.Syscall(CreateToolhelp32Snapshot,2, uintptr(TH32CS_SNAPPROCESS), uintptr(0),0)
if int(pHandle) == 0{
panic("CreateToolhelp32Snapshot调用失败")
}

Process32Next,err:=syscall.GetProcAddress(kernel32,"Process32Next")
if err != nil{
panic(Process32Next)
}

for {
var pe32 PROCESSENTRY32
pe32.dwSize = DWORD(unsafe.Sizeof(pe32))
ret, _, _ := syscall.Syscall(Process32Next,2,uintptr(pHandle), uintptr(unsafe.Pointer(&pe32)),0)
if int(ret) == 1 {
fmt.Println("进程 pid:",pe32.th32ProcessID,"进程名称:",string(pe32.szExeFile[0:]),"线程数量:",pe32.cntThreads)
} else {
break
}
}
}

其他的api调用套路都是这样,其他就需要是对文档熟悉程度,用到哪块就调用哪块就行了。

最后的最后,一个简易网页版进程管理:https://gitee.com/iwhot/task-manager

Go web 分页

这是一个自己写的比较简单的web分页,按钮样式主要是根据bootstrap调整的,如有需要的话可以自行调整:

package page

import (
	"html/template"
	"math"
	"strconv"
)

type page struct {
	Pre      string //上一页样式
	Next     string //下一页样式
	Count    int    //总数
	NowPage  int    //当前页
	PageSize int    //一页条数
	Current  int    //当前页
	PrePage  int    //上一页
	NextPage int    //下一页
	MaxNum   int    //页数按钮显示多少个
}

var Pagination = newPage()

func newPage() *page {
	return &page{
		Pre:    "«",
		Next:   "»",
		MaxNum: 10,
	}
}

func (p *page) Pagination(nowPage, pageSize, count int, param string) template.HTML {
	p.NowPage = nowPage
	p.Count = count
	if nowPage <= 1 {
		nowPage = 1
	}
	//总页数
	num := float64(count) / float64(pageSize)
	pageCount := int(math.Ceil(num))

	if count <= 1 || pageCount <= 1 {
		return template.HTML(`<ul class="pagination"><li class="disabled"><span>` + p.Pre + `</span></li><li class="active"><span>1</span></li><li class="disabled"><span>` + p.Next + `</span></li></ul>`)
	}

	p.Current = nowPage //当前页
	p.Count = pageCount //总页数

	if nowPage <= 1 {
		p.PrePage = 1 //上一页
	} else {
		p.PrePage = nowPage - 1 //上一页
	}

	if nowPage >= pageCount {
		p.NextPage = pageCount //下一页
	} else {
		p.NextPage = nowPage + 1 //下一页
	}

	var htmls string
	//拼接上面的
	htmls = `<ul class="pagination">`
	if p.Current <= 1 {
		htmls += `<li class="disabled"><span>` + p.Pre + `</span></li>`
	} else {
		htmls += `<li><a href="?p=` + strconv.Itoa(p.PrePage) + `">` + p.Pre + `</a></li>`
	}

	htmls += middleStr(p.Current, pageCount, p.MaxNum, param)

	//拼接底页
	if p.Current >= pageCount {
		htmls += `<li class="disabled"><span>` + p.Next + `</span></li>`
	} else {
		htmls += `<li><a href="?p=` + strconv.Itoa(p.NextPage) + `">` + p.Next + `</a></li>`
	}

	htmls += `</ul>`

	return template.HTML(htmls)
}

//中间部分拼接
func middleStr(current, pageCount, maxCount int, param string) string {
	var htmls string
	var i int
	//如果总页数不是很多就全部展示出来
	if pageCount <= maxCount {
		for i = 1; i <= pageCount; i++ {
			if current == i {
				htmls += `<li class="active"><span>` + strconv.Itoa(i) + `</span></li>`
			} else {
				if param != "" {
					htmls += `<li><a href="?p=` + strconv.Itoa(i) + `">` + strconv.Itoa(i) + `</a></li>`
				} else {
					htmls += `<li><a href="?` + param + `&p=` + strconv.Itoa(i) + `">` + strconv.Itoa(i) + `</a></li>`
				}

			}
		}
		return htmls
	}
	//如果分页比较多就展示部分按钮,其他部分用...代替
	if current > 1 {
		htmls += `<li><a href="?p=1">...</a></li>`
	}
	//获取最大值
	maxCount -= 1
	now := maxCount + current
	if now >= pageCount {
		now = pageCount
	}
	//里面必须有特定个数的按钮
	cnum := now - maxCount
	for i = cnum; i <= now; i++ {
		if current == i {
			htmls += `<li class="active"><span>` + strconv.Itoa(i) + `</span></li>`
		} else {
			if param != "" {
				htmls += `<li><a href="?p=` + strconv.Itoa(i) + `">` + strconv.Itoa(i) + `</a></li>`
			} else {
				htmls += `<li><a href="?` + param + `&p=` + strconv.Itoa(i) + `">` + strconv.Itoa(i) + `</a></li>`
			}
		}
	}
	//...结尾
	if now < pageCount && current >= 1 {
		htmls += `<li><a href="?p=` + strconv.Itoa(now) + `">...</a></li>`
	}
	return htmls
}

用法如下:

pagine := page.Pagination.Pagination(page, pageSize, count, param)

返回的是html,可以直接传入模板使用。效果如下:

go mod的使用

按照当前的趋势估计go之后的版本都是用go mod来管理了,gopath这种模式感觉会被淘汰。

go mod help查看帮助
go mod init<项目模块名称>初始化模块,会在项目根目录下生成 go.mod文件。
go mod tidy根据go.mod文件来处理依赖关系。
go mod vendor将依赖包复制到项目下的 vendor目录。建议一些使用了被墙包的话可以这么处理,方便用户快速使用命令go build -mod=vendor编译
go list -m all显示依赖关系。go list -m -json all显示详细依赖关系。
go mod download 下载依赖。参数是非必写的,path是包的路径,version是包的版本。

基本上用的时候就是,首先go init一下,然后编译的时候会把对应git上的包自己编译进去,编译之前可以go tidy处理一些依赖关系。如果网速慢也可以用vendor下载下来。

在此建议使用go mod的时候建议配置上goproxy,毕竟国内被墙的厉害,就是环境变量加个 GOPROXY=https://goproxy.io 就ok。