DEV Community

Cover image for Call Go function from C function
Yasuhiro Matsumoto
Yasuhiro Matsumoto

Posted on

Call Go function from C function

#go

Cgo is useful to embed C function into Go.

package main

/*
void doSomething(int *p) {
  *p = 123;
}
*/
import "C"

func main() {
    var n C.int
    C.doSomething(&n)
    println(n)
}
Enter fullscreen mode Exit fullscreen mode

This print 123 which is set from C. And works as intended with no problem. So in your first look, you might think you will not met any issue in future.

Please imagine the case when you want to call APIs which take a callback function and pointer of user_data.

APIs

typedef void (*callback)(void *);
void register_callback(callback cb, void *user_data);
void wait_event();
Enter fullscreen mode Exit fullscreen mode

As you guess, register_callback registers a callback function, wait_event waits for some event, and calls the callback function when an event occured.

Go

func my_callback(v unsafe.Pointer) {
}

func main() {
    user_data := "my userdata"
    C.register_event(/* ? */, /* ? */)
    C.wait_event()
}
Enter fullscreen mode Exit fullscreen mode

Do you wonder how to pass my_callback or user_data into register_event? Unfortunately, type of my_callback in Go is not same as C.callback. In short, you can't write below.

C.register_event(my_callback, &user_data) // compilation errors
Enter fullscreen mode Exit fullscreen mode

To make a function which is possible to be called from C, hack is required.

  • export Go function to be called from C
  • pass valid pointer from Go

To export Go function, put comment line //export FuncName above function.

//export hello
func hello() {
    // do something
}
Enter fullscreen mode Exit fullscreen mode

Wow this is simplify. Thus, it is possible make callback proxy.

package main

/*
#include <myapi.h>

void cb_proxy(void *v);

static void _register_callback(void *user_data) {
  register_callback(cb_proxy, user_data);
}
*/
import "C"
import (
    "fmt"
)

func my_callback(v string) {
    fmt.Println("hello", v)
}

func main() {
    C._register_callback(...)
    C.wait_event()
}

//export cb_proxy
func cb_proxy(v unsafe.Pointer) {
    // call my_callback
}
Enter fullscreen mode Exit fullscreen mode

_register_callback is wapper of register_callback to call cb_proxy. wait_event will invoke callback internally with taking argument user_data. user_data should be passed from main. However, one another issue you meet.

Rules for passing pointers between Go and C

Unfortunately again, Go can't pass pointer which is allocated in Go into C function.

But that's possible. See https://github.com/mattn/go-pointer. This package exchange pointers between Go and C.

var s string
C.pass_pointer(pointer.Save(&s))
v := *(pointer.Restore(C.get_from_pointer()).(*string))
Enter fullscreen mode Exit fullscreen mode

As the trick, go-pointer allocate 1-byte dummy memory for passing to C. And it is pointed to the value. i.e. this is unique key of map which is related on the real Go pointer. pointer.Save(&v) store Go pointer into the map and return C pointer which is allocated as dummy.

package main

/*
#include <myapi.h>

void cb_proxy(void *v);

static void _register_callback(void *user_data) {
  register_callback(cb_proxy, user_data);
}
*/
import "C"
import (
    "fmt"
    "unsafe"

    "github.com/mattn/go-pointer"
)

type Callback struct {
    Func     func(string)
    UserData string
}

func my_callback(v string) {
    fmt.Println("hello", v)
}

func main() {
    C._register_callback(pointer.Save(&Callback{
        Func:     my_callback,
        UserData: "my-callback",
    }))
    C.wait_event()
}

//export cb_proxy
func cb_proxy(v unsafe.Pointer) {
    cb := pointer.Restore(v).(*Callback)
    cb.Func(cb.UserData)
}
Enter fullscreen mode Exit fullscreen mode

_register_callbackpass cb_proxy. And cb_proxy restore the poinetr as *Callback. And it call original Callback.Func with Callback.UserData. You can pass callback function to C from Go.

You can try this behavior on this code.

package main

/*
typedef void (*callback)(void *);

static callback _cb;
static void *_user_data;
static void register_callback(callback cb, void *user_data) {
    _cb = cb;
    _user_data = user_data;
}
static void wait_event() {
    _cb(_user_data);
}

void cb_proxy(void *v);

static void _register_callback(void *user_data) {
  register_callback(cb_proxy, user_data);
}
*/
import "C"
import (
    "fmt"
    "unsafe"

    "github.com/mattn/go-pointer"
)

type Callback struct {
    Func     func(string)
    UserData string
}

func my_callback(v string) {
    fmt.Println("hello", v)
}

func main() {
    C._register_callback(pointer.Save(&Callback{
        Func:     my_callback,
        UserData: "my-callback",
    }))
    C.wait_event()
}

//export cb_proxy
func cb_proxy(v unsafe.Pointer) {
    cb := pointer.Restore(v).(*Callback)
    cb.Func(cb.UserData)
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)