18-Go語言和C語言交叉訪問
摘要:
Go語言中呼叫C語言函式
在Go語言開篇中我們已經知道, Go語言與C語言之間有著千絲萬縷的關係, 甚至被稱之為21世紀的C語言
所以在Go與C語言互操作方面,Go更是提供了強大的支援。尤其是在Go中使用C,你甚至可以直接在Go原始檔中編寫C程式碼,這是其他語言所無法望其...
Go語言中呼叫C語言函式
- 在Go語言開篇中我們已經知道, Go語言與C語言之間有著千絲萬縷的關係, 甚至被稱之為21世紀的C語言
- 所以在Go與C語言互操作方面,Go更是提供了強大的支援。尤其是在Go中使用C,你甚至可以直接在Go原始檔中編寫C程式碼,這是其他語言所無法望其項背的
-
格式:
- 在import "C"之前通過單行註釋或者通過多行註釋編寫C語言程式碼
- 在import "C"之後編寫Go語言程式碼
- 在Go語言程式碼中通過C.函式名稱() 呼叫C語言程式碼即可
- 注意: import "C"和前面的註釋之間不能出現空行或其它內容, 必須緊緊相連
package main //#include <stdio.h> //void say(){ // printf("Hello World\n"); //} import "C" func main(){ C.say() }
package main /* #include <stdio.h> void say(){ printf("Hello World\n"); } */ import "C" func main(){ C.say() }
- Go語言中沒有包名是C的包, 但是這個匯入會促使Go編譯器利用cgo工具預處理檔案
- 在預處理過程中,cgo會產生一個臨時包, 這個包裡包含了所有C函式和型別對應的Go語言宣告
- 最終使得cgo工具可以通過一種特殊的方式來呼叫import "C"之前的C語言程式碼
-
常規問題:
-
如果編譯報錯
cc1.exe: sorry, unimplemented: 64-bit mode not compiled in
- 說明你使用的是64的golang,而你使用的32位的MinGW,所以需要下載64位的mingw並配置環境變數
- 下載地址:ofollow,noindex">https://www.baidu.com/s?wd=sorry%2C%20unimplemented%3A%2064-bit%20mode%20not%20compiled%20in
-
如果編譯報錯
C語言中呼叫Go語言函式(很少使用)
-
在Go程式碼中通過
//export Go函式名稱
匯出Go的函式名稱 -
在C程式碼中通過
extern 返回值型別 Go函式名稱(形參列表);
宣告Go中匯出的函式名稱 -
注意:
//export Go函式名稱
和extern 返回值型別 Go函式名稱(形參列表);
不能在同一個檔案中
package main import "C" import "fmt" // 匯出Go函式宣告, 給C使用 //export GoFunction func GoFunction() { fmt.Println("GoFunction!!!") }
package main /* #include <stdio.h> // 宣告Go中的函式 extern void GoFunction(); void CFunction() { printf("CFunction!\n"); GoFunction(); } */ import "C" func main(){ C.CFunction() }
- 由於不在同一個檔案, 所以需要通過go build或者go install同時編譯多個檔案
- Go中使用C語言的型別
-
基本資料型別
- 在Go中可以用如下方式訪問C原生的數值型別:
C.char, C.schar (signed char), C.uchar (unsigned char), C.short, C.ushort (unsigned short), C.int, C.uint (unsigned int), C.long, C.ulong (unsigned long), C.longlong (long long), C.ulonglong (unsigned long long), C.float, C.double
- Go的數值型別與C中的數值型別不是一一對應的。因此在使用對方型別變數時必須顯式轉型操作
package main /* #include <stdio.h> int num = 123; float value = 3.14; char ch = 'N'; */ import "C" import "fmt" func main(){ var num1 C.int = C.num fmt.Println(num1) var num2 int //num2 = num1 // 報錯 num2 = int(num1) fmt.Println(num2) var value1 C.float = C.value fmt.Println(value1) var value2 float32 = float32(C.value) fmt.Println(value2) var ch1 C.char = C.ch fmt.Println(ch1) var ch2 byte = byte(C.ch) fmt.Println(ch2) }
-
字串型別
C.GoString(str) C.CString(str)
package main /* #include <stdio.h> char *str = "www.it666.com"; void say(char *name){ printf("my name is %s", name); } */ import "C" import ( "fmt" "unsafe" ) func main(){ // 1.C語言字串轉換Go語言字串 str1 := C.str str2 := C.GoString(str1) fmt.Println(str2) // 2.Go語言字串轉換C語言字串 str := "lnj" cs := C.CString(str) C.say(cs) // 注意: 轉換後所得到的C字串cs並不能由Go的gc所管理,我們必須手動釋放cs所佔用的記憶體 C.free(unsafe.Pointer(cs)) }
-
指標型別
- 原生數值型別的指標型別可按Go語法在型別前面加上*,例如:var p *C.int。
-
而void*比較特殊,用Go中的unsafe.Pointer表示。
- unsafe.Pointer:通用指標型別,用於轉換不同型別的指標,不能進行指標運算
- uintptr:用於指標運算,GC 不把 uintptr 當指標,uintptr 無法持有物件。uintptr 型別的目標會被回收
- 也就是說 unsafe.Pointer 是橋樑,可以讓任意型別的指標實現相互轉換,也可以將任意型別的指標轉換為uintptr 進行指標運算
package main /* #include <stdio.h> int num = 123; void *ptr = # */ import "C" import ( "fmt" "unsafe" ) func main(){ // 這是一個C語言變數 var num C.int = C.num // 這是一個C語言指標 var p1 *C.int = &num fmt.Println(*p1) //var p2 *C.void = C.ptr // 報錯 // 利用unsafe.Pointer接收viod * var p2 unsafe.Pointer = C.ptr // 將unsafe.Pointer轉換為具體型別 var p3 *C.int = (*C.int)(p2) fmt.Println(*p3) }
-
列舉型別
- C語言中的列舉型別在Go語言中的表現形式為C.enum_XXX
- 訪問列舉和訪問普通變數無異, 直接通過C.列舉值即可
package main /* #include <stdio.h> enum Gender { GenderMale, GenderFemale, GenderYao }; */ import "C" import "fmt" func main(){ var sex C.enum_Gender = C.GenderMale fmt.Println(sex) sex = C.GenderFemale fmt.Println(sex) sex = C.GenderYao fmt.Println(sex) }
-
結構體型別
結構體變數.屬性名稱
package main /* #include <stdio.h> struct Point { float x; float y; }; */ import "C" import ( "fmt" ) func main(){ // 1.利用C的結構體型別建立結構體 var cp C.struct_Point = C.struct_Point{6.6, 8.8} fmt.Println(cp) fmt.Printf("%T\n", cp) // 2.將C語言結構體轉換為Go語言結構體 type GoPoint struct { x float32 y float32 } var gp GoPoint gp.x = float32(cp.x) gp.y = float32(cp.y) fmt.Println(gp) }
-
陣列型別
- C語言中的陣列與Go語言中的陣列差異較大, C中的陣列是指標型別,Go中的陣列是值型別
- 目前似乎無法直接顯式的在兩者之間進行轉型,官方文件也沒有說明。
package main /* #include <stdio.h> int cArray[5] = {1, 2, 3, 4, 5}; */ import "C" import "fmt" func main(){ var cArr [5]C.int = C.cArray fmt.Println(cArr) fmt.Printf("%T\n", cArr) }
-
利用Go語言呼叫C語言函式, 實現無緩衝區輸入
- 請在終端執行
package main /* #include <stdio.h> char lowerCase(char ch){ // 1.判斷當前是否是小寫字母 if(ch >= 'a' && ch <= 'z'){ return ch; } // 注意點: 不能直接編寫else, 因為執行到else不一定是一個大寫字母 else if(ch >= 'A' && ch <= 'Z'){ return ch + ('a' - 'A'); } return ' '; } char getCh(){ // 1.接收使用者輸入的資料 char ch; scanf("%c", &ch); setbuf(stdin, NULL); // 2.大小寫轉換 ch = lowerCase(ch); // 3.返回轉換好的字元 return ch; } */ import "C" import "fmt" func main(){ for{ fmt.Println("請輸入w a s d其中一個字元, 控制小人走出迷宮") var ch byte = byte(C.getCh()) fmt.Printf("%c", ch) } }