Go处理DLL动态库

go生成DLL #

go写dll动态库

package main
import (
    "net"
)
//必须导入
import "C"
//编译成动态库也是必须的
func main() {}
//export Interfaces
func Interfaces(list []string, retlen *int) string {
    interf, err := net.InterfaceAddrs()
    if err != nil {
        return err.Error()
    }
    for i, v := range interf {
        if i >= len(list) {
            break
        }
        list[i] = v.String()
    }
    *retlen = len(list)
    return ""
}
go build -ldflags "-s -w" -buildmode=c-shared -o net.dll main.go

执行后,在同级目录下生成了net.h和net.dll文件

C语言中使用

#include <stdio.h>
#include<stdlib.h>
#include <string.h>
#include "net.h"
int main()
{ 
    GoString ret;
    GoSlice slice;
    slice.len=10;
    slice.cap=0;
    slice.data=calloc(10,sizeof(GoString));
    GoInt retlen=0;
    ret=Interfaces(slice,&retlen);
    if (ret.n != 0)
    {
        char* retc = calloc(ret.n+1,sizeof(char));
        memcpy(retc,ret.p,ret.n);
        printf("Return value:%s\n", retc); 
        free(retc);
        retc=NULL;
    }
    GoString* st=(GoString*)(slice.data);
    for (int i=0;i<retlen;i++)
    {
        printf("%s\n", st[i].p);
    }
    free(slice.data);
    slice.data=NULL;
    return 0;
}

C/C++生成DLL #

hello.h头文件

#ifndef _HELLO_H_
#define _HELLO_H_
#include <stdio.h>
#define HELLO_EXPORTS
#ifdef HELLO_EXPORTS
#define EXPORTS_API extern "C" __declspec(dllexport)
#else
#define EXPORTS_API extern "C" __declspec(dllimport)
#endif // HELLO_EXPORTS
EXPORTS_API int add(int left, int right);
EXPORTS_API void show(char* ptr, int nLen);
EXPORTS_API char* change(char* ptr, int nLen);
EXPORTS_API void callByReference(int& nLen);
EXPORTS_API void callByPtr(int* nLen);
#endif //_HELLO_H_

hello.cpp 代码文件

#include "hello.h"

int add(int left, int right)
{
 return left + right;
}

void show(char* ptr,int nLen)
{
 printf("> -------------------\n> Pass `pointer` and `int` data:\n");
 printf(">> %s, %d\n", ptr,nLen);
}

char* change(char* ptr, int nLen)
{
 if (!ptr || 0 > nLen)
  return nullptr;
 printf("> -------------------\n> Pass `pointer` and `int` data:\n");
 printf("> src strings: %s\n",ptr);
 ptr[1] = 'a';
 printf("> modify strings: %s\n", ptr);
 return ptr;
}

void callByReference(int& nLen)
{
 nLen = 100;
}

void callByPtr(int* nLen)
{
 *nLen = 1000;
}

GO使用C++代码 #

Go不能直接使用C++代码,要把C++代码转化为C

hello.h

#include <stdio.h>
extern int add(int left, int right);
extern void show(char* ptr, int nLen);
extern char* change(char* ptr, int nLen);
extern void callByReference(int& nLen);
extern void callByPtr(int* nLen);

修改hello.cpp代码

return nullptr; //nullptr是c++代码
void callByReference(int& nLen) //int& 是c++代码

main.go

//#include "hello.h"
import "C"
import "fmt"

func main()  {
	r,_ := C.add(C.int(12),C.int(34))
	fmt.Println(int(r))
}

//运行
env CGO_ENABLED=1 go build 

Go 调用DLL库 #

上面c++代码生成dll

g++ hello.cpp -fPIC -shared -o libc2plus.dll

转化支撑函数

//int => uintptr
func IntPtr(n int) uintptr {
	return uintptr(n)
}

//int -> *int -> unsafe.pointer -> uintptr
func Int2IntPtr(n int) uintptr {
	return uintptr(unsafe.Pointer(&n))
}
//*int -> unsafe.pointer -> uintptr
func IntPtr2Ptr(n *int) uintptr {
	return uintptr(unsafe.Pointer(n))
}

//[]byte -> *[]byte -> unsafe.pointer -> uintptr
func BytePtr(s []byte) uintptr {
	return uintptr(unsafe.Pointer(&s[0]))
}

//ret为uintptr临时变量,不能使用*(*int)(unsafe.Pointer(ret))去获取
func PtrInt(r uintptr) int {
	return  int(r)
}

获取DLL地址、错误提示函数

const (
	DLLPATH  = "libc2plus.dll"
)

func GetDllPath() string  {
	path, err := filepath.Abs(DLLPATH)
	abort("GetDLLPATH",err)
	return path
}

func abort(fn string, err error)  {
	if err != nil {
		panic(fn + err.Error())
	}
}

通过syscall.LoadLibrary获取和调用DLL #

调用c++的add函数

func add(a,b int) int  {
	fmt.Println(GetDllPath())
	handle,err := syscall.LoadLibrary(GetDllPath())
	abort("add syscall.LoadLibrary",err)

	defer syscall.FreeLibrary(handle)

	proc, err := syscall.GetProcAddress(handle, "add")
	abort("add syscall.GetProcAddress",err)
	if proc == 0 {
		abort("add syscall.GetProcAddress",errors.New("proce address is null"))
	}

	if ret, _, callErr := syscall.Syscall(proc, 2, IntPtr(a), IntPtr(b), 0); callErr != 0 {
		panic("add syscall.Syscall"+ callErr.Error())
	} else {
		//ret为uintptr临时变量,不能使用*(*int)(unsafe.Pointer(ret))去获取
		return  PtrInt(ret)
	}

	return 0
}

通过syscall.NewLazyDLL调用 #

调用c++的show函数,通常调用都使用该方式

func show(str []byte, len int)  {
	dll := syscall.NewLazyDLL(GetDllPath())
	show := dll.NewProc("show")
	show.Call(BytePtr(str),IntPtr(len))
}

调用c++的callByPtr函数

func passByPtr(n *int)  {
	dll := syscall.NewLazyDLL(GetDllPath())
	show := dll.NewProc("callByPtr")
	show.Call(IntPtr2Ptr(n))
}

通过 syscall.MustLoadDL调用 #

调用c++语言的change函数

func change(str []byte, len int) {
	handle := syscall.MustLoadDLL(GetDllPath())
	change := handle.MustFindProc("change")
	_, _, _ = change.Call(BytePtr(str),IntPtr(len))

	defer handle.Release()
}

通过window.NewLazySystemDLL调用 #

调用c++的callByReference值传递引用

func passByValue(n int)  {
	handle := windows.NewLazySystemDLL(GetDllPath())
	proc := handle.NewProc("callByReference")
	_, _, _ = proc.Call(Int2IntPtr(n))
	fmt.Println()
}

//传递的是地址就能获取到n的变化
func passByValue1(n *int)  {
	handle := windows.NewLazySystemDLL(GetDllPath())
	proc := handle.NewProc("callByReference")
	_, _, _ = proc.Call(IntPtr2Ptr(n))
	fmt.Println()
}

主函数 #

func main()  {
	sum := add(12,34)
	fmt.Println("call c++ add",sum)

	str := []byte("function execute")
	show(str,len(str))
	change(str,len(str))

	i := 0
	passByValue(i)
	fmt.Printf("pass by value %d\n",i)

	passByValue1(&i)
	fmt.Printf("pass by value1 %d\n",i)

	passByPtr(&i)
	fmt.Printf("pass by pointer %d\n",i)
}

输出

call c++ add 46      //add 

pass by value 0      //passByValue
pass by value1 100   //passByValue1
pass by pointer 1000 //passByPtr
> -------------------
> Pass `pointer` and `int` data: //show
>> function execute, 16
> -------------------
> Pass `pointer` and `int` data:  //change
> src strings: function execute
> modify strings: fanction execute

注意 #

1、c++中的char*,go中要通过[]byte传递参数地址,不能使用string

2、环境变量中要设置CGO_ENABLED=1

3、调用其它DLL,如果是386交叉编译,要加上参数GOARCH=386;CGO_ENABLED=1

不加会报错 %1 is not a valid Win32 application”

4、要加入import “C”,要不报错

package shadu/hello: C++ source files not allowed when not using cgo or SWIG: hello.cpp