golang go語言與C語言互調,通過cgo

來源:互聯網
上載者:User
1. good links:


http://tonybai.com/2012/09/26/interoperability-between-go-and-c/
http://www.mischiefblog.com/2014/06/26/example-cgo-golang-app-that-calls-a-native-library-with-a-c-structure/
http://tonybai.com/2016/02/21/some-changes-in-go-1-6/
http://blog.giorgis.io/cgo-examples
http://tonybai.com/tag/cgo/
http://cholerae.com/2015/05/17/%E4%BD%BF%E7%94%A8Cgo%E7%9A%84%E4%B8%80%E7%82%B9%E6%80%BB%E7%BB%93/
http://akrennmair.github.io/golang-cgo-slides/#1


2. cgo rules for c/c++


2.1. Mapping the C namespace to Go:


2.1.1 Everything declared in the C code is available in the C pseudo-package
2.1.2 Fundamental C data types have their counterpart, e.g. int → C.int, unsigned short → C.ushort, etc.
2.1.3 The Go equivalent to void * is unsafe.Pointer
2.1.4 typedefs are available under their own name
2.1.5 structs are available with a struct_ prefix, e.g. struct foo → C.struct_foo, same goes for unions and enums

2.2. Conversion between C and Go strings:


2.2.1. The C package contains conversion functions to convert Go to C strings and vice versa
2.2.2. Also: opaque data (behind void *) to []byte
2.2.3. Go string to C string; result must be freed with C.free : func C.CString(string) *C.char
2.2.4. C string to Go string: func C.GoString(*C.char) string
2.2.5. C string, length to Go string: func C.GoStringN(*C.char, C.int) string
2.2.6. C pointer, length to Go []byte: func C.GoBytes(unsafe.Pointer, C.int) []byte

2.3. Go調用C Code時,Go傳遞給C Code的Go Pointer所指的Go Memory中不能包含任何指向Go Memory的Pointer:


2.3.1. 傳遞一個指向Struct的指標

//cgo1_struct.go
package main


/*
#include <stdio.h>
struct Foo{
    int a;
    int *p;
};


void plusOne(struct Foo *f) {
    (f->a)++;
    *(f->p)++;
}
*/
import "C"
import "unsafe"
import "fmt"


func main() {
    f := &C.struct_Foo{}
    f.a = 5
    f.p = (*C.int)((unsafe.Pointer)(new(int)))
    //f.p = &f.a


    C.plusOne(f)
    fmt.Println(int(f.a))
}
從cgo1_struct.go代碼中可以看到,Go code向C code傳遞了一個指向Go Memory(Go分配的)指標f,
但f指向的Go Memory中有一個指標p指向了另外一處Go Memory: new(int),我們來run一下這段代碼:
$go run cgo1_struct.go
# command-line-arguments
./cgo1_struct.go:12:2: warning: expression result unused [-Wunused-value]
panic: runtime error: cgo argument has Go pointer to Go pointer


goroutine 1 [running]:
panic(0x4068400, 0xc82000a110)
    /Users/tony/.bin/go16/src/runtime/panic.go:464 +0x3e6
main.main()
    /Users/tony/test/go/go16/cgo/cgo1_struct.go:24 +0xb9
exit status 2
代碼出現了Panic,並提示:“cgo argument has Go pointer to Go pointer”。我們的代碼違背了Cgo Pointer路由規則,
即便讓f.p指向struct自身記憶體也是不行的,比如f.p = &f.a。

2.3.2. 傳遞一個指向struct field的指標:

按照rules中的說明,如果傳遞的是一個指向struct field的指標,那麼”Go Memory”專指這個field所佔用的記憶體,
即便struct中有其他field指向其他Go memory也不打緊:

//cgo1_structfield.go
package main


/*
#include <stdio.h>
struct Foo{
    int a;
    int *p;
};


void plusOne(int *i) {
    (*i)++;
}
*/
import "C"
import (
    "fmt"
    "unsafe"
)


func main() {
    f := &C.struct_Foo{}
    f.a = 5
    f.p = (*C.int)((unsafe.Pointer)(new(int)))


    C.plusOne(&f.a)
    fmt.Println(int(f.a))
}
上述程式的運行結果:


$go run cgo1_structfield.go
6

2.3.3. 傳遞一個指向slice or array中的element的指標:

和傳遞struct field不同,傳遞一個指向slice or array中的element的指標時,
需要考慮的Go Memory的範圍不僅僅是這個element,
而是整個Array或整個slice背後的underlying array所佔用的記憶體地區,
要保證這個地區內不包含指向任意Go Memory的指標。我們來看程式碼範例:
//cgo1_sliceelem.go
package main


/*
#include <stdio.h>
void plusOne(int **i) {
    (**i)++;
}
*/
import "C"
import (
    "fmt"
    "unsafe"
)


func main() {
    sl := make([]*int, 5)
    var a int = 5
    sl[1] = &a
    C.plusOne((**C.int)((unsafe.Pointer)(&sl[0])))
    fmt.Println(sl[0])
}
從這個代碼中,我們看到我們傳遞的是slice的第一個element的地址,即&sl[0]。我們並未給sl[0]賦值,
但sl[1] 被賦值為另外一塊go memory的address(&a),當我們將&sl[0]傳遞給plusOne時,執行結果如下:
$go run cgo1_sliceelem.go
panic: runtime error: cgo argument has Go pointer to Go pointer


goroutine 1 [running]:
panic(0x40dbac0, 0xc8200621d0)
    /Users/tony/.bin/go16/src/runtime/panic.go:464 +0x3e6
main.main()
    /Users/tony/test/go/go16/cgo/cgo1_sliceelem.go:19 +0xe4
exit status 2

2.4. C調用Go Code時:

2.4.1. C調用的Go函數不能返回指向Go分配的記憶體的指標:

package main


// extern int* goAdd(int, int);
//
// static int cAdd(int a, int b) {
//     int *i = goAdd(a, b);
//     return *i;
// }
import "C"
import "fmt"


//export goAdd
func goAdd(a, b C.int) *C.int {
    c := a + b
    return &c
}


func main() {
    var a, b int = 5, 6
    i := C.cAdd(C.int(a), C.int(b))
    fmt.Println(int(i))
}


可以看到:goAdd這個Go函數返回了一個指向Go分配的記憶體(&c)的指標。運行上述代碼,結果如下:
$go run cgo2_1.go
panic: runtime error: cgo result has Go pointer


goroutine 1 [running]:
panic(0x40dba40, 0xc82006e1c0)
    /Users/tony/.bin/go16/src/runtime/panic.go:464 +0x3e6
main._cgoexpwrap_872b2f2e7532_goAdd.func1(0xc820049d98)
    command-line-arguments/_obj/_cgo_gotypes.go:64 +0x3a
main._cgoexpwrap_872b2f2e7532_goAdd(0x600000005, 0xc82006e19c)
    command-line-arguments/_obj/_cgo_gotypes.go:66 +0x89
main._Cfunc_cAdd(0x600000005, 0x0)
    command-line-arguments/_obj/_cgo_gotypes.go:45 +0x41
main.main()
    /Users/tony/test/go/go16/cgo/cgo2_1.go:20 +0x35
exit status 2

2.4.2. Go code不能在C分配的記憶體中儲存指向Go分配的記憶體的指標:

//cgo2_2.go
package main


// #include <stdlib.h>
// extern void goFoo(int**);
//
// static void cFoo() {
//     int **p = malloc(sizeof(int*));
//     goFoo(p);
// }
import "C"


//export goFoo
func goFoo(p **C.int) {
    *p = new(C.int)
}


func main() {
    C.cFoo()
}
不過針對此例,預設的GODEBUG=cgocheck=1偏是無法check出問題。我們將GODEBUG=cgocheck改為=2試試:
$GODEBUG=cgocheck=2 go run cgo2_2.go
write of Go pointer 0xc82000a0f8 to non-Go memory 0x4300000
fatal error: Go pointer stored into non-Go memory


runtime stack:
runtime.throw(0x4089800, 0x24)
    /Users/tony/.bin/go16/src/runtime/panic.go:530 +0x90
runtime.cgoCheckWriteBarrier.func1()
    /Users/tony/.bin/go16/src/runtime/cgocheck.go:44 +0xae
runtime.systemstack(0x7fff5fbff8c0)
    /Users/tony/.bin/go16/src/runtime/asm_amd64.s:291 +0x79
runtime.mstart()
    /Users/tony/.bin/go16/src/runtime/proc.go:1048
... ...
goroutine 17 [syscall, locked to thread]:
runtime.goexit()
    /Users/tony/.bin/go16/src/runtime/asm_amd64.s:1998 +0x1
exit status 2
果真runtime panic: write of Go pointer 0xc82000a0f8 to non-Go memory 0×4300000






2.3. Calling Go code from C (1)


Go code can be exported via comment: //export <function-name>
It can then be called from C
package foo


/*
extern void myprint(int i);


void dofoo(void) {
int i;
for (i=0;i<10;i++) {
myprint(i);
}
}
*/
import "C"

//export myprint
func myprint(i C.int) {
fmt.Printf("i = %v\n", uint32(i))
}


func DoFoo() {
C.dofoo()
}


3. cgo examples:


package pwnam


//basic rule 1: 所有C代碼都要放到注釋當中.
/*
#include <sys/types.h>
#include <pwd.h>
#include <stdlib.h>
*/


//basic rule 2: 必須匯入"C", "unsafe"包,用於go與c互操作.
import ( "C" ; "unsafe" )


type Passwd struct {
Uid uint32 ; Gid uint32 ; Dir string ; Shell string
}


func Getpwnam(name string) *Passwd {
cname := C.CString(name) //basic rule 3: go string to c string.
defer C.free(unsafe.Pointer(cname)) //basic rule 4: must clean mannually and obviously.
cpw := C.getpwnam(cname) //basic rule 5: call C function, types, variables and etc all by C namespace.

return &Passwd{
Uid: uint32(cpw.pw_uid), Gid: uint32(cpw.pw_uid),
Dir: C.GoString(cpw.pw_dir), Shell: C.GoString(cpw.pw_shell)} //basic rule 6: c string to go string.
}


4. cgo compilation and link example:


#cgo LDFLAGS:
package pcap
/*
#cgo LDFLAGS: -lpcap
#include <stdlib.h>
#include <pcap.h>
*/
import "C"
#cgo pkg-config:
package stfl
/*
#cgo pkg-config: stfl
#cgo LDFLAGS: -lncursesw
#include <stdlib.h>
#include <stfl.h>
*/
import "C"




5. cgo 記憶體管理大原則


go runtime只管go的記憶體, 不管C,反之C只管自己也不管go

所以go的記憶體有GC照看, C的記憶體要自己C.free。



待續。。。


注意: 此文章只是我個人筆記, 如有錯漏,請一定指正, 共同學習, 我的郵箱: htyu_0203_39@sina.com








相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.