Giao diện
🔗 CGO & FFI
"CGO is not Go." — Rob Pike
CGO cho phép gọi C code, nhưng đổi lại là mất đi nhiều ưu điểm của Go.
CGO cho phép gọi C code, nhưng đổi lại là mất đi nhiều ưu điểm của Go.
⚠️ CGO Trade-offs
🔥 CGO = Complexity
| Benefit | Cost |
|---|---|
| Access C libraries | No cross-compilation |
| Reuse existing code | Slower builds |
| System-level access | No static binary |
| Memory safety risks | |
| Debugging difficulty |
⚔️ Tradeoff: CGO vs Alternatives
| Approach | Performance | Portability | Complexity | When to Use |
|---|---|---|---|---|
| CGO | Native | Poor | High | Required C library |
| Pure Go | Good | Excellent | Low | Rewrite possible |
| Subprocess | Overhead | Good | Medium | CLI tools |
| gRPC/FFI | RPC overhead | Good | Medium | Service boundary |
| WASM | Moderate | Excellent | Medium | Sandboxed code |
📐 CGO Basics
Simple C Call
go
package main
/*
#include <stdio.h>
#include <stdlib.h>
void greet(const char* name) {
printf("Hello, %s from C!\n", name);
}
int add(int a, int b) {
return a + b;
}
*/
import "C"
import (
"fmt"
"unsafe"
)
func main() {
// Call C function
name := C.CString("Go Developer")
defer C.free(unsafe.Pointer(name)) // Must free!
C.greet(name)
// Call with return value
result := C.add(C.int(5), C.int(3))
fmt.Printf("5 + 3 = %d\n", int(result))
}Build Requirements
bash
# CGO requires C compiler
# Windows: MinGW-w64 or MSVC
# Linux: gcc
# macOS: Xcode Command Line Tools
# Build with CGO enabled (default)
$ CGO_ENABLED=1 go build
# Check CGO status
$ go env CGO_ENABLED🔧 Type Conversions
Go ↔ C Type Mapping
go
/*
Type mappings:
Go | C
-----------|------------
C.char | char
C.schar | signed char
C.uchar | unsigned char
C.short | short
C.int | int
C.long | long
C.longlong | long long
C.float | float
C.double | double
*/
// String conversion
func cString() {
// Go string → C string
goStr := "Hello"
cStr := C.CString(goStr) // Allocates C memory!
defer C.free(unsafe.Pointer(cStr))
// C string → Go string
goStrBack := C.GoString(cStr)
// With length
goStrN := C.GoStringN(cStr, C.int(5))
}
// Byte slice conversion
func cBytes() {
goBytes := []byte{0x01, 0x02, 0x03}
// Go []byte → C void*
cPtr := unsafe.Pointer(&goBytes[0])
// C void* → Go []byte
// Use slicing or copy
}💾 Memory Management
💥 Critical: Memory Ownership
Rule 1: Memory allocated in C must be freed in C. Rule 2: Memory allocated in Go is managed by GC. Rule 3: Passing Go pointers to C has restrictions.
C Memory Allocation
go
/*
#include <stdlib.h>
#include <string.h>
typedef struct {
int id;
char name[50];
} User;
User* create_user(int id, const char* name) {
User* u = (User*)malloc(sizeof(User));
u->id = id;
strncpy(u->name, name, 49);
u->name[49] = '\0';
return u;
}
void free_user(User* u) {
free(u);
}
*/
import "C"
import "unsafe"
func main() {
name := C.CString("Alice")
defer C.free(unsafe.Pointer(name))
// Create C struct (allocates in C heap)
user := C.create_user(C.int(1), name)
defer C.free_user(user) // Must free!
fmt.Printf("User: id=%d, name=%s\n",
int(user.id),
C.GoString(&user.name[0]))
}Go Pointer Rules
go
/*
// cgo pointer passing rules (since Go 1.6):
// 1. Go code may pass a Go pointer to C if it points to memory
// that doesn't contain any Go pointers.
// 2. C code may not keep a copy of a Go pointer after the call returns.
// 3. C code may not share a Go pointer with other C code.
*/
// ❌ Violates rules - contains Go pointer
type BadStruct struct {
next *BadStruct // Go pointer inside
}
// ✅ OK - no Go pointers
type SafeStruct struct {
id int32
data [100]byte
}
func passToC() {
safe := SafeStruct{id: 42}
// OK - no internal Go pointers
C.process((*C.SafeStruct)(unsafe.Pointer(&safe)))
}📚 Linking External Libraries
Using System Library
go
// #cgo LDFLAGS: -lm
// #include <math.h>
import "C"
func main() {
result := C.sqrt(C.double(16))
fmt.Println("sqrt(16) =", float64(result))
}Platform-Specific Flags
go
/*
#cgo CFLAGS: -I/usr/local/include
#cgo LDFLAGS: -L/usr/local/lib
#cgo linux LDFLAGS: -lsqlite3
#cgo darwin LDFLAGS: -lsqlite3
#cgo windows LDFLAGS: -lsqlite3.dll
#include <sqlite3.h>
*/
import "C"Pkg-config Integration
go
/*
#cgo pkg-config: openssl
#include <openssl/sha.h>
*/
import "C"💻 Engineering Example: SQLite Wrapper
go
package sqlite
/*
#cgo LDFLAGS: -lsqlite3
#include <sqlite3.h>
#include <stdlib.h>
// Helper: create error string
static char* get_error(sqlite3* db) {
return (char*)sqlite3_errmsg(db);
}
*/
import "C"
import (
"errors"
"unsafe"
)
// DB wraps SQLite database connection
type DB struct {
db *C.sqlite3
}
// Open opens a SQLite database
func Open(path string) (*DB, error) {
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
var db *C.sqlite3
rc := C.sqlite3_open(cPath, &db)
if rc != C.SQLITE_OK {
if db != nil {
errMsg := C.GoString(C.get_error(db))
C.sqlite3_close(db)
return nil, errors.New(errMsg)
}
return nil, errors.New("failed to open database")
}
return &DB{db: db}, nil
}
// Close closes the database connection
func (d *DB) Close() error {
if d.db == nil {
return nil
}
rc := C.sqlite3_close(d.db)
if rc != C.SQLITE_OK {
return errors.New(C.GoString(C.get_error(d.db)))
}
d.db = nil
return nil
}
// Exec executes a SQL statement
func (d *DB) Exec(sql string) error {
cSQL := C.CString(sql)
defer C.free(unsafe.Pointer(cSQL))
var errMsg *C.char
rc := C.sqlite3_exec(d.db, cSQL, nil, nil, &errMsg)
if rc != C.SQLITE_OK {
err := C.GoString(errMsg)
C.sqlite3_free(unsafe.Pointer(errMsg))
return errors.New(err)
}
return nil
}
// Usage
func main() {
db, err := Open(":memory:")
if err != nil {
log.Fatal(err)
}
defer db.Close()
err = db.Exec(`CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)`)
if err != nil {
log.Fatal(err)
}
err = db.Exec(`INSERT INTO users (name) VALUES ('Alice')`)
if err != nil {
log.Fatal(err)
}
fmt.Println("SQLite operations completed")
}🔄 Avoiding CGO
Pure Go Alternatives
go
// Instead of CGO SQLite:
import "modernc.org/sqlite" // Pure Go SQLite
// Instead of CGO image processing:
import "golang.org/x/image/draw"
// Instead of CGO crypto:
import "crypto/sha256" // Pure Go in stdlib
// Instead of CGO regexp (PCRE):
import "regexp" // RE2 in pure GoWhen CGO is Unavoidable
- Calling proprietary vendor SDKs
- Hardware driver interfaces
- Legacy C library with no Go port
- Performance-critical code already in C
✅ Ship-to-Prod Checklist
Build & Deploy
- [ ] CGO_ENABLED=0 if not needed (smaller binaries)
- [ ] Cross-compilation tested (or use Docker)
- [ ] Static linking if possible (
-ldflags '-extldflags "-static"') - [ ] Library versions pinned and documented
Memory Safety
- [ ] All C allocations freed (C.free, custom free functions)
- [ ] No Go pointers stored in C beyond call duration
- [ ] String conversion handled correctly (CString → free)
- [ ] Memory leaks tested with Valgrind/AddressSanitizer
Testing
- [ ] CGO code isolated in separate package
- [ ] Tests run with -race flag
- [ ] Memory sanitizer used in CI
- [ ] Multiple platforms tested
📊 Summary
| Aspect | Recommendation |
|---|---|
| Default | Avoid CGO if possible |
| Build | Use Docker for cross-platform CGO |
| Memory | Always free C allocations |
| Pointers | Follow cgo pointer rules strictly |
| Alternative | Prefer pure Go libraries |
➡️ Tiếp theo
CGO nắm vững rồi! Tiếp theo: Performance Tuning - Profiling và optimization.