Go语言调用动态库
golang的OLE调用工具包 https://github.com/mattn/go-ole
GCC环境 https://sourceforge.net/projects/mingw-w64/files/
设置环境变量后在控制台中输入
可以看到gcc版本号,说明gcc安装成功
编写go程序 我们这里只是编写一个简单的输出字符串的程序,接受一个字符串参数,然后将拼接成新的字符串并作为返回值返回,在这里,我们将文件命名为libhello.go
1 2 3 4 5 6 7 8 9 10 package mainimport "C" func hello (msg string ) string { return "Hello! " + msg } func main () {}
注意,即使是要编译成动态库,也要有main函数,上面的import "C"
一定要有
Windows动态库 执行如下命令生成DLL动态链接库:
1 go build -buildmode=c-shared -o libtest.dll .\test.go
如果控制台没有报错,那么会在当前路径下生成libhello.dll文件
Linux/Unix/macOS动态库 执行如下命令生成SO动态库:
1 go build -buildmode=c-shared -o libtest.so test.go
Go调用windows dll syscall.Syscall系列方法 当前共5个方法
1 2 3 4 5 syscall.Syscall syscall.Syscall6 syscall.Syscall9 syscall.Syscall12 syscall.Syscall15
分别对应 3个/6个/9个/12个/15个参数或以下的调用
参数都形如
1 syscall.Syscall(trap, nargs, a1, a2, a3)
第二个参数, nargs 即参数的个数,一旦传错, 轻则调用失败,重者直接APPCARSH,多余的参数, 用0代替,跟Syscall系列一样, Call方法最多15个参数。Must开头的方法, 如不存在,会panic.
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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 package mainimport ( "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()) } func IntPtr (n int ) uintptr { return uintptr (n) } func StrPtr (s string ) uintptr { return uintptr (unsafe.Pointer(syscall.StringToUTF16Ptr(s))) } func ShowMessage1 (caption, text string , style uintptr ) (result int ) { user32, _ := syscall.LoadLibrary("user32.dll" ) messageBox, _ := syscall.GetProcAddress(user32, "MessageBoxW" ) defer syscall.FreeLibrary(user32) 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 ShowMessage2 (title, text string ) { user32 := syscall.NewLazyDLL("user32.dll" ) MessageBoxW := user32.NewProc("MessageBoxW" ) MessageBoxW.Call(IntPtr(0 ), StrPtr(text), StrPtr(title), IntPtr(0 )) } 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 ShowMessage4 (title, text string ) { user32 := syscall.MustLoadDLL("user32.dll" ) MessageBoxW := user32.MustFindProc("MessageBoxW" ) MessageBoxW.Call(IntPtr(0 ), StrPtr(text), StrPtr(title), IntPtr(0 )) } func main () { num := ShowMessage1("Done Title" , "windows下的第一种DLL方法调用" , MB_YESNOCANCEL) fmt.Printf("Get Retrun Value Before MessageBox Invoked: %d\n" , num) ShowMessage2("windows下的第二种DLL方法调用" , "HELLO !" ) ShowMessage3("windows下的第三种DLL方法调用" , "HELLO !" ) ShowMessage4("windows下的第四种DLL方法调用" , "HELLO !" ) time.Sleep(3 * time.Second) } func init () { fmt.Println("Start..." )
c实现的dll foo.h
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 #ifndef _FOO_H_ #define _FOO_H_ #include <stdio.h> #define FOO_EXPORTS #ifdef FOO_EXPORTS #define EXPORTS_API __declspec(dllexport) #else #define EXPORTS_API __declspec(dllimport) #endif #ifdef __cplusplus extern "C" {#endif 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) ; #ifdef __cplusplus } #endif #endif
foo.cpp
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 #include "foo.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 ; }
编译
1 2 # 生成window下的动态库和静态库 gcc -shared -o foo.dll foo.c -Wl,--output-def,foo.def,--out-implib,foo.a
go调用代码 编写调用dll的代码:
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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 package mainimport ( "fmt" "strconv" "syscall" "unsafe" "C" ) var dllPath = "F:\\goproject\\src\\plugin\\foo.dll" func main () { call() } func IntPtr (n int ) uintptr { return uintptr (n) } func Int2IntPtr (n int ) uintptr { return uintptr (unsafe.Pointer(&n)) } func IntPtr2Ptr (n *int ) uintptr { return uintptr (unsafe.Pointer(n)) } func BytePtr (s []byte ) uintptr { return uintptr (unsafe.Pointer(&s[0 ])) } func call () error { left := 4 right := 5 err := Add(left, right) if err != nil { fmt.Println("Error:" , err) return err } str := []byte ("this is a test msg!" ) err = Show(str, len (str)) if err != nil { fmt.Println("Error:" , err) return err } err = Change_bytes(str, len (str)) if err != nil { fmt.Println("Error:" , err) return err } n := 0 err = Call_PassByValue(n) if err != nil { fmt.Println("Error:" , err) return err } fmt.Println("> Call_PassByValue(n)的结果为 n=" + strconv.Itoa(n) + ",期待输出 100" ) n = 0 err = Call_PassByPtr(&n) if err != nil { fmt.Println("Error:" , err) return err } fmt.Println("> Call_PassByPtr(&n)的结果为 n=" + strconv.Itoa(n) + ",期待输出 1000" ) return nil } func Add (left, right int ) error { handle, err := syscall.LoadLibrary(dllPath) if err != nil { fmt.Printf("Error: %s\n" , err) return err } defer syscall.FreeLibrary(handle) add, err := syscall.GetProcAddress(handle, "add" ) if err != nil { fmt.Printf("Error: %s\n" , err) return err } ret, _, _ := syscall.Syscall(add, 2 , IntPtr(left), IntPtr(right), 0 ) if err != nil { fmt.Printf("Error: %s\n" , err) } fmt.Println("> Add(4,5)的结果为:" , ret) return nil } func Show (str []byte , l int ) error { handle := syscall.NewLazyDLL(dllPath) show := handle.NewProc("show" ) show.Call(BytePtr(str), IntPtr(l)) return nil } func Change_bytes (str []byte , l int ) error { handle := syscall.NewLazyDLL(dllPath) change := handle.NewProc("change" ) change.Call(BytePtr(str), IntPtr(l)) return nil } func Call_PassByValue (n int ) error { handle := syscall.NewLazyDLL(dllPath) test := handle.NewProc("callByReference" ) test.Call(Int2IntPtr(n)) return nil } func Call_PassByPtr (n *int ) error { handle := syscall.NewLazyDLL(dllPath) test := handle.NewProc("callByPtr" ) test.Call(IntPtr2Ptr(n)) return nil }
1、当值传递时并没有修改传入的值;只有指针传递时修改了传入的值。 2、指针传递时golang侧使用的是byte切片
结论 1、需要修改参数的值时,必须使用指针类型
1 2 3 func Call_PassByPtr (n *int ) error { return nil }
2、需要修改指针的内容时,必须使用指针类型
1 2 3 func Change_bytes (str []byte , l int ) error { return nil }
3、golang传递指针给c接口函数时,必须使用[] byte的类型,不能使用string类型
1 2 3 4 5 6 7 8 9 func Show (str []byte , l int ) error { dllPath := "F:\\goproject\\src\\plugin\\foo.dll" handle := syscall.NewLazyDLL(dllPath) show := handle.NewProc("show" ) show.Call(BytePtr(str), IntPtr(l)) return nil }
Go中嵌入C代码 Golang中嵌入C代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package main import "C" import "unsafe" func main () { s := "Hello Cgo" cs := C.CString(s) C.Hello(cs) C.free(unsafe.Pointer(cs)) }
Go调用C的动态库so C库源程序代码:
1 2 3 4 5 6 7 8 9 #include <stdio.h> int Num = 8 ;void foo () { printf ("I dont have new line." ); printf ("I have new line\n" ); printf ("I have return\r" ); printf ("I have return and new line\r\n" ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #ifndef __FOO_H #define __FOO_H #ifdef __cplusplus extern "C" {#endif extern int Num;extern void foo () ; #ifdef __cplusplus } #endif #endif
编译
1 2 3 4 5 6 gcc foo.c -fPIC -shared -o libfoo.so gcc -c foo.c -o foo.o //产生目标文件 ar rcv libfoo.a foo.o //将目标文件打包成静态库
Go调用C库的代码:
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 package mainimport "C" import "unsafe" import "fmt" func Prin (s string ) { cs := C.CString(s) defer C.free(unsafe.Pointer(cs)) C.fputs(cs, (*C.FILE)(C.stdout)) C.fflush((*C.FILE)(C.stdout)) } func main () { fmt.Println("vim-go" ) fmt.Printf("rannum:%x\n" , C.random()) Prin("Hello CC" ) fmt.Println(C.Num) C.foo() }
如果c库的程序改为下面这样:
1 2 3 4 5 6 7 8 9 10 11 #include <stdio.h> int Num = 8 ;void foo () { printf ("I dont have new line." ); }
此时,go中调用foo时没有任何反应,即不会输出,不会输出的原因是printf后面没有加换行符,但是如果加了8,9,10这些测试行后,第7行也会显示,原因是第10行最后有一个换行符,这个应该是向stdout输出时,字符流是放在buffer中,如果没有换行,buffer中的数据不会立即输出。在go调用C的测试程序中有写了一个测试小函数用来测试stdout,验证了没有fflush,stdout上不会显示输出信息。但平时在写C程序的时候,似乎没有这样的问题,这个是因为C的运行环境自动做了fflush的动作。
Go调用C的静态库a Go调用的代码与动态库一样
Go参数类型转换为C参数类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int EC60789803D1(const char *CompanyTag, char *dataIn, int inlen, unsigned char *dataout, int outlen);{ var byteOutBuffer []byte byteOutBuffer = make ([]byte , 2048 ) var outlen int = 2048 var strKey string strKey = "test" var inString string inString = "fdsafdsafdsa" fmt.Println("inlen=" , len (inString)) InKey := C.CString(strKey) InData := C.CString(inString) InInLen := C.int (len (inString)) InOut := (*C.uchar)( &byteOutBuffer[0 ]) InOutLen := C.int (outlen) iRet := C.EC60789803D1(InKey, InData, InInLen, InOut, InOutLen) fmt.Println("C.EC60789803D1 ret=" , iRet, " OutBuffer=" , util.Bytes2str(byteOutBuffer)) }
cgo可以在go语言中夹杂着C函数或数据,在使用cgo时,有一些需要注意的: 1、go中的int/int32/int64/uint32/uint64和C语言中的int/int32等是不同的,因此,C语言的函数的参数不能是go语言的int,需要转换,同理,go函数的int也不能使用C的int,需要转换。
C.int(n)
。还有一点,C的函数调用中,有很多参数是size_t类型,其实就是一个整型,但如果使用C.int()
作为C函数的参数,就会编译出错:
1 cannot use _Ctype_int(100) (type C.int) as type C.size_t in function argument
go编译器严格限制参数类型必须一致,因此必须是size_t类型的参数。这是因为go语言没有C语言里面的强制转换的概念,你可以使用C.size_t(n)
来得到C语言中的sizt_t类型。 2、go语言中的字符串和C语言中的字符串转换
3、结构体 使用C.struct_xxxx
如果使用struct中的成员变量,可以直接用.
来访问。 4、指针 使用unsafe.Pointer()
来转换,例如需要转换为 int *:
1 (*C.int )(unsafe.Pointer(&v))
在c语言中,指针即数组,可以使用ptr[n]来获得指针的第n个偏移,但在go中,这样不行,会报错:invalid operation:xxxx
,go语言中,指针没有这样的操作。 需要使用unsafe.Pointer
和uintptr
配合来获取指针的偏移。 5、函数调用C.func()
,文档中说,go调用所有的C函数都会返回两个值,后一个值为error类型,即使是void函数。文档表述如下:
1 2 n, err := C.sqrt(-1 ) _, err := C.voidFunc()
但发现,C.malloc似乎只返回一个值。 6、C语言中的NULL在go中是nil 例如
1 2 3 4 s := C.malloc(C.sizeof(100 )) if s == nil {… }
这个很重要,在没发现nil可以比较c的指针前,是这样比较的:
1 (*C.char)(unsafe.Pointer(uintptr (0 )))
不过,cgo的文档还很匮乏,很多都需要阅读代码。
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 package mainimport “C”import “unsafe”import “fmt”func main () { var t C.struct_t var s = “hello world” var ch *C.char var tmp *C.char t.s = (*C.char)(C.malloc(C.size_t(100 ))) if t.s == nil { panic (“malloc failed!\n”) } defer C.free(unsafe.Pointer(t.s)) ch = C.CString(s) defer C.free(unsafe.Pointer(ch)) C.strncpy(t.s, ch, C.size_t(len (s))) for i := C.size_t(0 ); i < C.strlen(t.s); i ++ { tmp = (*C.char)(unsafe.Pointer(uintptr (unsafe.Pointer(t.s)) + uintptr (i))) *tmp = C.char(C.toupper(C.int (*tmp))) } fmt.Printf(“%s\n”, C.GoString(t.s)) fmt.Printf(“sizeof struct t is %v\n”, unsafe.Sizeof(t)) }
http://www.361way.com/go-plugin/5925.html