20.go调用lib和so动态库

go 调用dll

1. sysCall.LoadDll(推荐使用)

  • 系统调用是程序向操作系统内核请求服务的过程,通常包含硬件相关的服务(例如访问硬盘),创建新进程等。系统调用提供了一个进程和操作系统之间的接口

  • fmt中的syscall

    1
    2
    3
    4
    5
    
    func Println(a ...interface{}) (n int, err error) {
        return Fprintln(os.Stdout, a...)
    }
    
    Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
  • 调用dll 示例

    1
    2
    3
    4
    
    dll, err := syscall.LoadDLL("scan.dll")
    //根据名称从dll中查找proc
    MemoryStream_Get = dll.FindProc("AllocateMemory")
    MemoryStream_Get.Call()
  • 此方式可以 也可以调用go 代码打包的dll

2. Cgo调用

  • 项目目录结构如下

    1
    2
    3
    4
    5
    6
    
    ├── include
         └── add.c
         └── add.h
    ├── lib
         └── libadd.dll
    └── main.go
  • add.h

    1
    2
    3
    4
    5
    6
    
    #ifndef __ADD_H__
    #define __ADD_H__
    
    char* Add(char* src, int n);
    
    #endif
  • add.c

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    #include <string.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    char* Add(char* src, int n)
    {
        char str[20];
        sprintf(str, "%d", n);
        char *result = malloc(strlen(src)+strlen(str)+1);
        strcpy(result, src);
        strcat(result, str);
        return result;
    }
  • main.go

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    
    func GetFinalStrategyString(request string) {
    
    	dll, err := syscall.LoadDLL("./middleware_c.dll")
    	if err != nil {
    		log.Fatal("Error loading DLL:", err)
    	}
    	defer dll.Release()
    
    	getFinalStrategyString, err := dll.FindProc("GetFinalStrategyString")
    	if err != nil {
    		log.Fatal("Error finding GetFinalStrategyString:", err)
    	}
    
    	freeFinalStrategyString, err := dll.FindProc("FreeFinalStrategyString")
    	if err != nil {
    		log.Fatal("Error finding FreeFinalStrategyString:", err)
    	}
    
    	// cRequest, err := syscall.UTF16PtrFromString(request)
    	// if err != nil {
    	// 	log.Fatal("Error converting request:", err)
    	// }
    
    	var cResponse *uint16
    	var responseLen uint32
    
    	ret, _, _ := getFinalStrategyString.Call(
    		uintptr(unsafe.Pointer(request)),
    		uintptr(len(request)),
    		uintptr(unsafe.Pointer(&cResponse)),
    		uintptr(unsafe.Pointer(&responseLen)),
    	)
    
    	if ret != 0 {
    		log.Fatal("ret Error:", ret)
    	}
    	fmt.Println("responseLen:", responseLen)
    	fmt.Println("cResponse:", cResponse)
    
    	cResponseBytes := (*[1 << 20]byte)(unsafe.Pointer(cResponse))[:responseLen]
    	fmt.Println("cResponseBytes:", cResponseBytes)
    }

3. 动态调用 dll (推荐使用)

  • 动态调用步骤

    1. 通过文件路径加载c/c++ 动态库中的 handle
    2. 在go代码中定义和c/c++ 动态库中对应的go func
    3. 使用库purego(底层是dlopen)或者sys/windows 将handle中对应的方法符号(Symbol)映射到 go func 地址上
    4. 逻辑调用
  • main.go

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    
    import "C"
    import (
    	"context"
    	"fmt"
    	"github.com/ebitengine/purego"
    	"sync"
    )
    
    
    var (
    	getFinalStrategyString func(*C.char, C.uint, **C.char, *C.uint) C.int  //2. 定义和c/c++ 动态库中对应的go func
    	freeFinalStrategyString func(*C.char)  // 2. 定义和c/c++ 动态库中对应的go func
    	mcRunModeOnce sync.Once
    	mcRunMode     int
    )
    
    func main(){
        InitMC(libFilePath)
    }
    
    func InitMC(libFilePath string) (err error) {
    	defer func() {
    		if _err := recover(); _err != nil {
    			logger.Errorf("RegisterLibFunc panic error: %v", err)
    			err = fmt.Errorf("RegisterLibFunc panic error: %v", err.Error())
    		}
    	}()
    	...
    	mcHandle, err := LoadHandle(libFilePath)  // 1. 通过路径加载c/c++ 动态库handle
    	if err != nil {
    		logger.Errorf("LoadHandle error:%v", err.Error())
    		return err
    	}
        // 找不到函数会panic
    	purego.RegisterLibFunc(&getFinalStrategyString, mcHandle, "GetFinalStrategyString") // 3. 使用库purego将handle中对应的方法映射到go func
    	purego.RegisterLibFunc(&freeFinalStrategyString, mcHandle, "FreeFinalStrategyString")  // 3. 使用库purego将handle中对应的方法映射到go func
    	return nil
    }
  • load_linux.go

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    //go:build !windows
    // +build !windows
    
    package main
    
    import "github.com/ebitengine/purego"
    
    const MCLibPath = "c.so"
    
    func LoadHandle(libPath string) (uintptr, error) {
    	return purego.Dlopen(libPath, purego.RTLD_NOW|purego.RTLD_GLOBAL)
    }
  • load_windows.go

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    //go:build windows
    // +build windows
    
    package main
    
    import "golang.org/x/sys/windows"
    
    const MCLibPath = ""
    
    func LoadHandle(libPath string) (uintptr, error) {
    	handle, err := windows.LoadLibrary(libPath)
    	return uintptr(handle), err
    }
  • 业务调用

    c++ 头文件

    #ifdef __cplusplus extern “C” { #endif

    MIDDLEWARE_C_EXPORT int GetFinalStrategyString( /::browserconsoleapiv3::middle_ware::GetUserStrategiesRequest request/ const char* request, unsigned request_len, /::browserconsoleapiv3::api::user::FinalGroupStrategy response/ char** response, unsigned* response_len);

    MIDDLEWARE_C_EXPORT void FreeFinalStrategyString(const char*);

    #ifdef __cplusplus } #endif

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    
    package middleware
    
    /*
    #include <stdlib.h>  // 要引入
    */
    import "C"
    import (
    	"fmt"
    	"unsafe"
    )
    
    type MiddlewareC struct {
    	response *C.char
    }
    
    func (mc *MiddlewareC) GetFinalStrategyString(req string) (resByte []byte, err error) {
    	cRequest := C.CString(req)
    	defer C.free(unsafe.Pointer(cRequest))
    	var (
    		reqLen      C.uint
    		cResponse   *C.char
    		responseLen C.uint
    		resCodeC    C.int
    	)
    	reqLen = C.uint(len(req))
    	resCodeC = getFinalStrategyString(cRequest, reqLen, &cResponse, &responseLen)  //4. 真正的业务调用
    	resCode := int(resCodeC)
    	if resCode != 0 {
    		errMsg, ok := MCError[resCode]
    		if !ok {
    			err = fmt.Errorf(
    				"unknown error code, code:%v, cResponse: %v, cResponse len: %v",
    				resCode, cResponse, responseLen,
    			)
    		} else {
    			err = fmt.Errorf(
    				"middleware error msg: %v, error code: %v, cResponse: %v, cResponse len: %v",
    				errMsg, resCode, cResponse, responseLen,
    			)
    		}
    		return resByte, err
    	}
    	resByte = C.GoBytes(unsafe.Pointer(cResponse), C.int(responseLen))
    	mc.response = cResponse
    	return resByte, nil
    }
    
    func (mc *MiddlewareC) MustFreeFinalStrategyString() {
    	freeFinalStrategyString(mc.response)
    }

go 调用so

1. Cgo调用

  • 项目目录结构如下

    1
    2
    3
    4
    5
    6
    
    ├── include
         └── add.c
         └── add.h
    ├── lib
         └── libadd.so
    └── main.go
  • add.h

    1
    2
    3
    4
    5
    6
    
    #ifndef __ADD_H__
    #define __ADD_H__
    
    char* Add(char* src, int n);
    
    #endif
  • add.c

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    #include <string.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    char* Add(char* src, int n)
    {
        char str[20];
        sprintf(str, "%d", n);
        char *result = malloc(strlen(src)+strlen(str)+1);
        strcpy(result, src);
        strcat(result, str);
        return result;
    }
  • linux 下编译

    会在当前目录下生成 libadd.so 文件, 在 Linux 下可用 nm -D libadd.so 查看其中的方法

    1
    
    gcc -fPIC -shared -o lib/libadd.so include/add.c
  • main.go

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    package main
    
    /*
    // 头文件的位置,相对于源文件是当前目录,所以是 .,头文件在多个目录时写多个  #cgo CFLAGS: ...
    #cgo CFLAGS: -I./include
    // 从哪里加载动态库,位置与文件名,-ladd 加载 libadd.so 文件
    #cgo LDFLAGS: -L./lib -ladd -Wl,-rpath,lib
    #include "add.h"
    */
    import "C"
    import "fmt"
    
    func main() {
      val := C.Add(C.CString("go"), 2023)
      fmt.Println("run c: ", C.GoString(val))
    }
  • 注意:

    • 如果把#cgo LDFLAGS: -L./lib -ladd -Wl,-rpath,lib 改为 cgo LDFLAGS: -L./lib -ladd编译不会报错,执行时会出错

      1
      
      error while loading shared libraries: libadd.so: cannot open shared object file: No such file or directory
    • 设置了环境变量 LD_LIBRARY_PATH=/home/…/lib 也能让它跑起来

      1
      
      LD_LIBRARY_PATH=lib/ ./demo

2. 动态调用 so(推荐使用)

  • 动态调用步骤

    1. 通过路径加载c/c++ 动态库 handle
    2. 定义和c/c++ 动态库中对应的go func
    3. 使用库purego 将handle中对应的方法映射到go func
    4. 逻辑调用
  • main.go

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    
    import "C"
    import (
    	"context"
    	"fmt"
    	"github.com/ebitengine/purego"
    	"sync"
    )
    
    
    var (
    	getFinalStrategyString func(*C.char, C.uint, **C.char, *C.uint) C.int  //2. 定义和c/c++ 动态库中对应的go func
    	freeFinalStrategyString func(*C.char)  // 2. 定义和c/c++ 动态库中对应的go func
    	mcRunModeOnce sync.Once
    	mcRunMode     int
    )
    
    func main(){
        InitMC(libFilePath)
    }
    
    func InitMC(libFilePath string) (err error) {
    	defer func() {
    		if _err := recover(); _err != nil {
    			logger.Errorf("RegisterLibFunc panic error: %v", err)
    			err = fmt.Errorf("RegisterLibFunc panic error: %v", err.Error())
    		}
    	}()
    	...
    	mcHandle, err := LoadHandle(libFilePath)  // 1. 通过路径加载c/c++ 动态库handle
    	if err != nil {
    		logger.Errorf("LoadHandle error:%v", err.Error())
    		return err
    	}
        // 找不到函数会panic
    	purego.RegisterLibFunc(&getFinalStrategyString, mcHandle, "GetFinalStrategyString") // 3. 使用库purego将handle中对应的方法映射到go func
    	purego.RegisterLibFunc(&freeFinalStrategyString, mcHandle, "FreeFinalStrategyString")  // 3. 使用库purego将handle中对应的方法映射到go func
    	return nil
    }
  • load_linux.go

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    //go:build !windows
    // +build !windows
    
    package main
    
    import "github.com/ebitengine/purego"
    
    const MCLibPath = "c.so"
    
    func LoadHandle(libPath string) (uintptr, error) {
    	return purego.Dlopen(libPath, purego.RTLD_NOW|purego.RTLD_GLOBAL)
    }
  • load_windows.go

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    //go:build windows
    // +build windows
    
    package main
    
    import "golang.org/x/sys/windows"
    
    const MCLibPath = ""
    
    func LoadHandle(libPath string) (uintptr, error) {
    	handle, err := windows.LoadLibrary(libPath)
    	return uintptr(handle), err
    }
  • 业务调用

    c++ 头文件

    #ifdef __cplusplus extern “C” { #endif

    MIDDLEWARE_C_EXPORT int GetFinalStrategyString( /::browserconsoleapiv3::middle_ware::GetUserStrategiesRequest request/ const char* request, unsigned request_len, /::browserconsoleapiv3::api::user::FinalGroupStrategy response/ char** response, unsigned* response_len);

    MIDDLEWARE_C_EXPORT void FreeFinalStrategyString(const char*);

    #ifdef __cplusplus } #endif

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    
    package middleware
    
    /*
    #include <stdlib.h>  // 要引入
    */
    import "C"
    import (
    	"fmt"
    	"unsafe"
    )
    
    type MiddlewareC struct {
    	response *C.char
    }
    
    func (mc *MiddlewareC) GetFinalStrategyString(req string) (resByte []byte, err error) {
    	cRequest := C.CString(req)
    	defer C.free(unsafe.Pointer(cRequest))
    	var (
    		reqLen      C.uint
    		cResponse   *C.char
    		responseLen C.uint
    		resCodeC    C.int
    	)
    	reqLen = C.uint(len(req))
    	resCodeC = getFinalStrategyString(cRequest, reqLen, &cResponse, &responseLen)  //4. 真正的业务调用
    	resCode := int(resCodeC)
    	if resCode != 0 {
    		errMsg, ok := MCError[resCode]
    		if !ok {
    			err = fmt.Errorf(
    				"unknown error code, code:%v, cResponse: %v, cResponse len: %v",
    				resCode, cResponse, responseLen,
    			)
    		} else {
    			err = fmt.Errorf(
    				"middleware error msg: %v, error code: %v, cResponse: %v, cResponse len: %v",
    				errMsg, resCode, cResponse, responseLen,
    			)
    		}
    		return resByte, err
    	}
    	resByte = C.GoBytes(unsafe.Pointer(cResponse), C.int(responseLen))
    	mc.response = cResponse
    	return resByte, nil
    }
    
    func (mc *MiddlewareC) MustFreeFinalStrategyString() {
    	freeFinalStrategyString(mc.response)
    }

大坑!!!

  • 动态库中的崩溃会直接导致主程序崩溃!!!!!!!!!!

  • 崩溃测试

    • 创建崩溃程序

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      
      package main
      
      import "C"
      import "fmt"
      
      //export PrintTest
      func PrintTest() int {
      	defer func() {
      		if err := recover(); err != nil {
      			print("err\n")
      			print(err)
      		}
      	}()
      	print("hello world\n")
      	var a []int
      	a[0] = 1
      	print("hello world 2\n")
      	return 1
      }
      
      func main() {
      	fmt.Println("call cpp test")
      }
    • 打包dll文件go build -o pt.dll -buildmode=c-shared main.go

    • 主程序调用

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      
      package main
      
      import "C"
      import (
      	"github.com/ebitengine/purego"
      	win "golang.org/x/sys/windows"
      	"log"
      )
      
      var PrintTest func() int
      
      func openLibrary(name string) (uintptr, error) {
      	handle, err := win.LoadLibrary(name)
      	return uintptr(handle), err
      }
      
      func main() {
          // recover无效了!
      	defer func() {
      		if err := recover(); err != nil {
      			log.Println("err")
      			log.Println(err)
      		}
      	}()
      
      	lib, err := openLibrary("./dll/pt.dll")
      	if err != nil {
      		log.Fatalln(err)
      	}
      	purego.RegisterLibFunc(&PrintTest, lib, "PrintTest")
      	res := PrintTest()
      	log.Println(res)
      }
      
      // !!! 直接panic,recover无效
      
Buy me a coffee~
Fred 支付宝支付宝
Fred 微信微信
0%