經歷了數十年發展的C語言,各種各樣的現成的庫已經非常豐富。通過cgo,可以在Go語言中使用C語言代碼,充分利用好現有的“輪子”。
本文所有代碼,在下述環境中調試通過:
- Windows 8.1 64-bit
- Go 1.3.3 64-bit
- GCC 4.8.1 64-bit
要想使用cgo,要導入C“包”:
import "C"這行代碼的上方要 緊挨着 連續的若干行的注釋,在這些注釋中編寫C代碼。例如:
/* int PlusOne(int n) { return n + 1; } */ import "C"
我們知道,如果要引用一個包中的符號,需要用“包名.符號名”的方式,C“包”也是這樣,例如:C.int、C.GetWindowLongPtr。
下面介紹使用C語言變量、函數、結構體、聯合體、回調函數和動態鏈接庫(Dynamic Link Library,dll)的方法。
- 變量
- 函數
- 結構體
- 聯合體
- 回調函數
- dll
使用C的變量很簡單,比方說,要使用int,只要在Go代碼中寫C.int就可以了。
package main import ( "fmt" ) import "C" func main() { var n C.int n = 5 fmt.Println(n) // 5 var m1 int // Go不認為C.int與int、int32等類型相同 // 所以必須進行轉換 m1 = int(n + 3) fmt.Println(m1) // 8 var m2 int32 m2 = int32(n + 20) fmt.Println(m2) // 25 }
2. 函數
在Go中調用C的函數也不困難。
package main import ( "fmt" ) /* int PlusOne(int n) { return n + 1; } */ import "C" func main() { var n int = 10 var m int = int(C.PlusOne(C.int(n))) // 類型要轉換 fmt.Println(m) // 11 }
3. 結構體
package main import ( "fmt" ) /* typedef struct _POINT { double x; double y; }POINT; */ import "C" func main() { var p C.POINT p.x = 9.45 p.y = 23.12 fmt.Println(p) // {9.45 23.12} }
4. 聯合體
Go中使用C的聯合體是比較少見而奇怪的事情,而且稍顯麻煩,因為Go將C的聯合體視為字節數組。比方說,下面的聯合體LARGE_INTEGER被視為[8]byte。
typedef long LONG; typedef unsigned long DWORD; typedef long long LONGLONG; typedef union _LARGE_INTEGER { struct { DWORD LowPart; LONG HighPart; }; struct { DWORD LowPart; LONG HighPart; } u; LONGLONG QuadPart; } LARGE_INTEGER, *PLARGE_INTEGER;
所以,如果一個C的函數的某個參數的類型為LARGE_INTEGER,我們可以給它一個[8]byte類型的實參,反之亦然。
package main import ( "fmt" ) /* typedef long LONG; typedef unsigned long DWORD; typedef long long LONGLONG; typedef union _LARGE_INTEGER { struct { DWORD LowPart; LONG HighPart; }; struct { DWORD LowPart; LONG HighPart; } u; LONGLONG QuadPart; } LARGE_INTEGER, *PLARGE_INTEGER; void Show(LARGE_INTEGER li) { li.u.LowPart = 1; li.u.HighPart = 4; } */ import "C" func main() { var li C.LARGE_INTEGER // 等價於: var li [8]byte var b [8]byte = li // 正確,因為[8]byte和C.LARGE_INTEGER相同 C.Show(b) // 參數類型為LARGE_INTEGER,可以接收[8]byte li[0] = 75 fmt.Println(li) // [75 0 0 0 0 0 0 0] li[4] = 23 Test(li) // 參數類型為[8]byte,可以接收C.LARGE_INTEGER } func Test(b [8]byte) { fmt.Println(b) }
5. 回調函數
有些C函數的參數是回調函數,比方說:
typedef UINT_PTR(__stdcall* GIRL_PROC)(int); typedef UINT_PTR(__cdecl* GIRL_PROC_CDECL)(int); UINT_PTR Func1(int n, GIRL_PROC gp) { if (gp == NULL) { return 0; } return (*gp)(n); } UINT_PTR Func2(int n, GIRL_PROC_CDECL gp) { if (gp == NULL) { return 0; } return (*gp)(n); }
syscall包中有如下兩個函數:
syscall.NewCallback
syacall.NewCallbackCDecl
其中,第一個函數接收一個Go函數(這個Go函數的返回值必須只有一個,而且類型為uintptr),並生成一個__stdcall調用約定的C函數,並將生成的函數的地址以uintptr的形式返回;第二個函數的作用與之類似,但生成的函數的調用約定是__cdecl。
一個值得注意的問題是:C的指向函數的指針在Go中被視為*[0]byte,所以要轉換一下才能用。這里演示一下__stdcall調用約定的函數的用法,__cdecl類似。
package main import ( "fmt" "syscall" "unsafe" ) /* #define WIN32_LEAN_AND_MEAN #include <windows.h> typedef UINT_PTR(__stdcall* GIRL_PROC)(int); typedef UINT_PTR(__cdecl* GIRL_PROC_CDECL)(int); UINT_PTR Func1(int n, GIRL_PROC gp) { if (gp == NULL) { return 0; } return (*gp)(n); } UINT_PTR Func2(int n, GIRL_PROC_CDECL gp) { if (gp == NULL) { return 0; } return (*gp)(n); } */ import "C" func GirlProc(n int32) uintptr { return uintptr(n + 97) } func main() { gp := syscall.NewCallback(GirlProc) fmt.Println(gp) gop := (*[0]byte)(unsafe.Pointer(gp)) var t C.UINT_PTR = C.Func1(C.int(29), gop) fmt.Println(t) // 126 }
6. dll
以后再寫。