您现在的位置是:网站首页> Go语言

GO语言调用DLL相关技术收集

摘要

GO语言调用DLL相关技术收集


GO语言调用DLL,填平所有的坑,最详尽攻略

go 调用windows dll 的三种方法

Go动态调用dll

Go中调用C的动态库与静态库




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的动态库与静态库

最近在学习GO语言,作为一门基于C语言的语言。对于它的语法很快掌握了,其实本人很早就想写一些关于GO的文章了,今天抽空就写一下,费话不说了,直击主题好了。 以下代码是在Linux运行的结果,若用GDB调试,最好安装7.1以上版本

  一、Go调用C的动态库:
   1、创建C的头文件
        //  foo.h
         extern int Num;   // 提供给 go调用
        void foo();
    2、创建C的源文件
        // foo.c
          int Num = 6;
          void foo()
         {
             printf("I'm  Langston!\n");
          }
    3、创建go源文件
        // CgoTest project main.go          
         package main
         // #include "foo.h"
          import "C"
          import "fmt"
         
        func main()  {
           fmt.Println(C.Num)
           C.foo()
        }
     4、生成动态库(libfoo.so, 在Linux下叫共享库,我口误  )
        gcc -c foo.c
        gcc -shared -Wl,-soname,libfoo.so -o libfoo.so  foo.o
     5、使用go工具编译项目
         go build
     6、运行生成的执行档
        ./CgoTest
二、 Go调用C的静态库:
  只有第3、第4步不一样,其他都一样的。这里只针对这两步做处理。
  3、 创建go源文件
     // CgoTest project main.go
     package main
 
     // #cgo LDFLAGS: -L ./ -lfoo
     // #include "foo.h"
     import "C"
     import "fmt"
 
     func main() {
         fmt.Println(C.Num)
         C.foo()
     }
  4、生成静态库 (libfoo.a)
      gcc -c foo.c
      ar -rv libfoo.a foo.o










Top