Fundamentals 12 min read

Calling Go Functions from C: Step‑by‑Step Guide and Underlying Mechanism

This article explains how to invoke Go functions from C within the same process, covering the three‑step implementation, the required cgo compilation commands, the generated intermediate files, and the runtime mechanisms such as crosscall2 and stack switching that make cross‑language calls possible.

Refining Core Development Skills
Refining Core Development Skills
Refining Core Development Skills
Calling Go Functions from C: Step‑by‑Step Guide and Underlying Mechanism

Hello, I'm Fei. In this article we explore whether a language can call a function implemented in another language inside the same process, using C calling Go as an example.

Cross‑language calls within a single process are indeed feasible; languages provide interop mechanisms like Go's cgo and Java's JNI to reuse existing code.

The implementation consists of three steps: (1) define and export a Go function, (2) compile the Go code into a static or dynamic library, and (3) call the library from C.

1.1 Go function definition

package main

//int add(int a, int b);
import "C"

//export add
func add(a, b C.int) C.int {
    return a + b
}

func main() {}

Key points: the line import "C" enables cgo, the comment //int add(int a, int b) declares the C prototype, //export add makes the function visible to C, and parameters must use C.int types.

1.2 Compile Go to a library

# go build -buildmode=c-shared -o libadd.dylib main.go

The -buildmode=c-shared flag creates a dynamic library (use -buildmode=c-archive for a static archive). The output includes libadd.h and libadd.dylib (or .so on Linux).

1.3 C code that calls the library

#include
#include "libadd.h"

int main(void) {
    int ret = add(2, 3);
    printf("C calls Go function 2+3 = %d\n", ret);
    return 0;
}

Compile and link the C program, specifying the library location and name:

# gcc main.c -L. -ladd -o main

Running ./main prints "C调用Go函数2+3= 5", confirming the successful call.

2. Underlying implementation details

Running go tool cgo main.go generates intermediate files such as main.cgo1.go , main.cgo2.c , _cgo_gotypes.go , and the export stubs _cgo_export.c and _cgo_export.h inside the _obj directory.

The exported entry point in _cgo_export.c prepares a packed argument struct, ensures the Go runtime is initialized via _cgo_wait_runtime_init_done() , and then calls crosscall2 to transfer control to the Go runtime.

crosscall2 (implemented in runtime/cgo/asm_amd64.s ) saves host registers, adjusts the stack, and invokes runtime·cgocallback . The callback switches from the OS thread stack to a Go goroutine stack, locks the goroutine to the current thread, and finally calls the user‑defined Go function through a generated stub in _cgo_gotypes.go .

3. Summary

The cross‑language call flow consists of three cooperating parts: the user’s C and Go code, the cgo‑generated stub code, and the Go runtime (functions like crosscall2 , cgocallback , and related helpers). Compared with ordinary function calls, the interop adds overhead due to stack switching and runtime coordination, but it remains faster than RPC because it stays within the same process.

FFIGoRuntimeC++interoperabilitycgocross-language
Refining Core Development Skills
Written by

Refining Core Development Skills

Fei has over 10 years of development experience at Tencent and Sogou. Through this account, he shares his deep insights on performance.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.