您现在的位置是:网站首页> Go语言
GO语言调用DLL相关技术收集
- Go语言
- 2025-01-01
- 1137人已阅读
GO语言调用DLL相关技术收集
GO语言调用DLL,填平所有的坑,最详尽攻略
由于业务需要,购买了别人写好的一个DLL模块,于是磨难开始了,经历多天的折磨,终于完美解决。
首先DLL如果是32位的,编译前运行
set GOARCH=386
第一个DLL函数,第一个参数,要求传入一个指针,直接指向[]byte类型,注意,是直接指向;第2个参数为[]byte长度;第三个参数为一个直接指向string类型指针;返回一个整数,标识调用成功或失败。
最折磨我的就是直接指向某种类型的指针传递问题,查了N多资料,都是类似下面这样:
p:= unsafe.Pointer(&dat)
g:=dll32.NewProc("XXX")
r, _, _ :=g.Call(uintptr(p),uintptr(cdat),uintptr(pk))
我开始也这样用,怎么弄都不对,然后我用OD载入调试,发现传进去的东西根本不是DLL想要的。
这样传进去的数据会被2层指针指向,ptrA->ptrB->[]byte,传进去的是ptrA,所以导致无法正常调用。那么问题来了,怎么才能传进去的指针直接指向数据,达到类似ptrA->[]byte这样的效果呢?
问题的重点就在这里,研究了好几天uintptr发现不是它的问题,问题出在
unsafe.Pointer
它上面,它会在指针外面再包一层指针,怎么解决呢?我只能考虑先把指针转成整数再传进去,结果
p:= *((*int32)(unsafe.Pointer(&dat)))
r, _, _ :=g.Call(uintptr(p),uintptr(cdat),uintptr(pk))
这样成功了。下面传递整数指针就简单多了
cdat:=len(dat)
这样即可,再后面传递字符串指针,指针获取方式和byte一样即可。但是问题又来了,执行不成功,继续OD,发现有问题,问题在于GO语言字符串后面在内存中没有结尾标志。那GO自己怎么判断字符串结尾呢?我想应该是每个字符串GO都同时记录了长度吧,不过不确定,有明白的大神请告知,这个问题我就只能这样,先把字符串转换成byte,然后在byte最后加0,类似这样
keystr:=[]byte{49,50,51,0}
pk:= *((*int32)(unsafe.Pointer(&keystr)))
这个问题就解决了,这个字符串就变成windows识别的了。返回值整数,直接就能用,这点我很奇怪,不知道为什么,比如这里,可以直接
r, _, _ =g.Call(uintptr(p),uintptr(cdat),uintptr(pk))
if r!=1 {
按理说,返回的是个指向整数的指针,应该*r才对,不懂,大神告知。
然后现在所有传递参数的问题解决了,后面问题又来了,第2个函数,调用后返回值是指向字符串的指针,这个指针指向的内容字符串当然是0结尾的windows格式了,GO依然无法正确读取。怎么办呢,只能自己写了个函数处理这个问题
//根据DLL返回的指针,逐个取出字节,到0字节时判断为字符串结尾,返回字节数组转成的字符串
func prttostr(vcode uintptr) string {
var vbyte []byte
for i:=0;i<10;i++{
sbyte:=*((*byte)(unsafe.Pointer(vcode)))
if sbyte==0{
break
}
vbyte=append(vbyte,sbyte)
vcode += 1
}
return string(vbyte)
}
原理就是操作指针一直向后移动,发现0了就停止,这样问题解决了,虽然所有的资料和大神都告诉我,不要轻易操作指针,但是这种情况下,不这么弄,怎么弄呢?谁能告诉我。
无论如何问题终于解决了,也许解决的不够完美,我想应该有更简单的办法,但是我不知道。
go 调用windows dll 的三种方法
第三种方法是从Go\src\internal\syscall\windows\sysdll源码中找到的,三种方法的具体区别还不是很明晰,
初步判断:lazy应该是相当于动态库,其余两种直接把库加载到内存。
package main
import(
"fmt"
"syscall"
"time"
"unsafe"
)
const (
MB_OK = 0x00000000
MB_OKCANCEL = 0x00000001
MB_ABORTRETRYIGNORE = 0x00000002
MB_YESNOCANCEL = 0x00000003
MB_YESNO = 0x00000004
MB_RETRYCANCEL = 0x00000005
MB_CANCELTRYCONTINUE = 0x00000006
MB_ICONHAND = 0x00000010
MB_ICONQUESTION = 0x00000020
MB_ICONEXCLAMATION = 0x00000030
MB_ICONASTERISK = 0x00000040
MB_USERICON = 0x00000080
MB_ICONWARNING = MB_ICONEXCLAMATION
MB_ICONERROR = MB_ICONHAND
MB_ICONINFORMATION = MB_ICONASTERISK
MB_ICONSTOP = MB_ICONHAND
MB_DEFBUTTON1 = 0x00000000
MB_DEFBUTTON2 = 0x00000100
MB_DEFBUTTON3 = 0x00000200
MB_DEFBUTTON4 = 0x00000300
)
func abort(funcname string, err syscall.Errno) {
panic(funcname + " failed: " + err.Error())
}
var (
user32, _ = syscall.LoadLibrary("user32.dll")
messageBox, _ = syscall.GetProcAddress(user32, "MessageBoxW")
)
func IntPtr(n int) uintptr {
return uintptr(n)
}
func StrPtr(s string) uintptr {
return uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(s)))
}
func MessageBox(caption, text string, style uintptr) (result int) {
ret, _, callErr := syscall.Syscall9(messageBox,
4,
0,
StrPtr(text),
StrPtr(caption),
style,
0, 0, 0, 0, 0)
if callErr != 0 {
abort("Call MessageBox", callErr)
}
result = int(ret)
return
}
//func GetModuleHandle() (handle uintptr) {
// if ret, _, callErr := syscall.Syscall(getModuleHandle, 0, 0, 0, 0); callErr != 0 {
// abort("Call GetModuleHandle", callErr)
// } else {
// handle = ret
// }
// return
//}
// windows下的第二种DLL方法调用
func ShowMessage2(title, text string) {
user32 := syscall.NewLazyDLL("user32.dll")
MessageBoxW := user32.NewProc("MessageBoxW")
MessageBoxW.Call(IntPtr(0), StrPtr(text), StrPtr(title), IntPtr(0))
}
// windows下的第三种DLL方法调用
func ShowMessage3(title, text string) {
user32,_ := syscall.LoadDLL("user32.dll")
MessageBoxW,_ := user32.FindProc("MessageBoxW")
MessageBoxW.Call(IntPtr(0), StrPtr(text), StrPtr(title), IntPtr(0))
}
func main() {
defer syscall.FreeLibrary(user32)
num := MessageBox("Done Title", "This test is Done.", MB_YESNOCANCEL)
fmt.Printf("Get Retrun Value Before MessageBox Invoked: %d\n", num)
ShowMessage2("windows下的另一种DLL方法调用", "HELLO !")
ShowMessage3("windows下的第三种DLL方法调用", "lyslyslys !")
time.Sleep(3 * time.Second)
}
func init() {
fmt.Print("Starting Up\n")
}
Go动态调用dll
VC写一个 DLL
// TestDll.cpp : Defines the entry point for the DLL application.
//
#include "stdafx.h"
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
return TRUE;
}
int __stdcall myadd(int a,int b)
{
return a+b;
}
Go中动态调用
package main
import (
"fmt"
"syscall"
)
func main() {
fmt.Println("你好Hello World!")
addfile, err := syscall.LoadLibrary("TestDll.dll") //动态库加载
if err != nil {
fmt.Printf("cgo:err:%v\n", err)
}
myaddcall, _ := syscall.GetProcAddress(addfile, "myadd")
var nargs uintptr = 3
ret, _, callErr := syscall.Syscall(uintptr(myaddcall), nargs, 5, 3, 0)
if callErr != 0 {
fmt.Printf("call myaddcall:%v\n", callErr)
}
fmt.Printf("myadd:%v\n", ret)
}
syscall.Syscall系列方法
syscall.Syscall syscall.Syscall6 syscall.Syscall9 syscall.Syscall12 syscall.Syscall15
分别对应 3个/6个/9个/12个/15个参数或以下的调用
syscall.Syscall(trap, nargs, a1, a2, a3)
第二个参数, nargs 即参数的个数,一旦传错, 轻则调用失败,重者直接APPCARSH
多余的参数, 用0代替
调用示例
获取磁盘空间
//首先,准备输入参数, GetDiskFreeSpaceEx需要4个参数, 可查MSDNdir := "C:"lpFreeBytesAvailable := int64(0)
//注意类型需要跟API的类型相符lpTotalNumberOfBytes := int64(0)lpTotalNumberOfFreeBytes := int64(0)
//获取方法的引用kernel32, err := syscall.LoadLibrary("Kernel32.dll")
// 严格来说需要加上 defer syscall.FreeLibrary(kernel32)
// GetDiskFreeSpaceEx, err := syscall.GetProcAddress(syscall.Handle(kernel32), "GetDiskFreeSpaceExW")
//执行之. 因为有4个参数,故取Syscall6才能放得下. 最后2个参数,自然就是0了r, _, errno := syscall.Syscall6(uintptr(GetDiskFreeSpaceEx), 4,
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("C:"))),
uintptr(unsafe.Pointer(&lpFreeBytesAvailable)),
uintptr(unsafe.Pointer(&lpTotalNumberOfBytes)),
uintptr(unsafe.Pointer(&lpTotalNumberOfFreeBytes)), 0, 0)
// 注意, errno并非error接口的, 不可能是nil// 而且,根据MSDN的说明,返回值为0就fail, 不为0就是成功if r != 0 {
log.Printf("Free %dmb", lpTotalNumberOfFreeBytes/1024/1024)}
简单点的方式? 用syscall.Call
跟Syscall系列一样, Call方法最多15个参数. 这里用来Must开头的方法, 如不存在,会panic.
h := syscall.MustLoadDLL("kernel32.dll")
c := h.MustFindProc("GetDiskFreeSpaceExW")
lpFreeBytesAvailable := int64(0)
lpTotalNumberOfBytes := int64(0)
lpTotalNumberOfFreeBytes := int64(0)
r2, _, err := c.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("F:"))),
uintptr(unsafe.Pointer(&lpFreeBytesAvailable)),
uintptr(unsafe.Pointer(&lpTotalNumberOfBytes)),
uintptr(unsafe.Pointer(&lpTotalNumberOfFreeBytes)))
if r2 != 0 {
log.Println(r2, err, lpFreeBytesAvailable/1024/1024)
调用例子:
var (
// kernel32, _ = syscall.LoadLibrary("kernel32.dll")
// getModuleHandle, _ = syscall.GetProcAddress(kernel32, "GetModuleHandleW")
user32, _ = syscall.LoadLibrary("user32.dll")
messageBox, _ = syscall.GetProcAddress(user32, "MessageBoxW")
)
func IntPtr(n int) uintptr {
return uintptr(n)
}
func StrPtr(s string) uintptr {
return uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(s)))
}
func MessageBox(caption, text string, style uintptr) (result int) {
ret, _, callErr := syscall.Syscall9(uintptr(messageBox),
4,
0,
StrPtr(text),
StrPtr(caption),
style,
0, 0, 0, 0, 0)
if callErr != 0 {
abort("Call MessageBox", callErr)
}
result = int(ret)
return
}
Go中调用C的动态库与静态库