如何在C的多个程序包中重用Go回调?

Is there a way to build a Go + C application that:

  • From main package X, import packages Y and Z.
  • Package M exports a go callback F.
  • Packages X and Y are both built with accompanying C files, both want to call F from C source code.

Generally speaking I'm trying to figure out how to call a callback from accompanying C files in other modules which are used to build a final application. I coudn't figure out how to achieve this or something similar. I'm also interested in convoluted solutions.

I don't see a way to call a Go function across packages, but all cgo packages are linked into the same binary and can call each other. This means that you can export M.F to a C function in package M and call that C function from packages Y and Z.

m/m.go:

package m

// void F();
import "C"
import "fmt"

//export F
func F() {
    fmt.Println("m.f")
}

m/m.h:

void m_f();

m/m.c:

#include <stdio.h>
#include "_cgo_export.h"
#include "m.h"

void m_f() {
    printf("m_f
")
    F();
}

y/y.go:

package y

// The LDFLAGS lines below are needed to prevent linker errors
// since not all packages are present while building intermediate
// packages. The darwin build tag is used as a proxy for clang
// versus gcc because there doesn't seem to be a better way
// to detect this.

// #cgo darwin LDFLAGS: -Wl,-undefined -Wl,dynamic_lookup
// #cgo !darwin LDFLAGS: -Wl,-unresolved-symbols=ignore-all
// #include "y.h"
import "C"

import (
    "fmt"
    _ "m"
)

func Y() {
    fmt.Println("y.Y")
    C.y()
}

y/y.h:

void y();

y/y.c:

#include <stdio.h>
#include "../m/m.h"

void y() {
    printf("y.C.y
");
    m_f();
}

Here is an example, that will accept any go callback (not thread-safe).

b.go:

package b

// typedef void (*cbFunc) ();
// void do_run(cbFunc);
// void goCallback();
import "C"

//export goCallback
func goCallback() {
    if goCallbackHolder != nil {
        goCallbackHolder()
    }
}

var goCallbackHolder func()

func Run(callback func()) {
    goCallbackHolder = callback
    C.do_run(C.cbFunc(C.goCallback))
}

b.c:

#include "_cgo_export.h"

void do_run(void (*callback)())
{
    callback();
}

I couldn't make it to work in a simple fashion IMO.

Given main package X that imports Y and Z, both having to call (from C source code) F declared in package M,

I had to:

  1. Create a small wrapper W1 for F in Y and export it to be called from Y's C source.
  2. Create a small wrapper W2 for F in Z and export it to be called from Z's C source.
  3. In Y CGO CPPFLAGS define -DCALLBACK=W1
  4. In Z CGO CPPFLAGS define -DCALLBACK=W2
  5. From C source code, anywhere, I'm now able to refer to F as CALLBACK (yeah, internally it's all different stuff, which I refer to using a single name at one end to call a single function at the other end).

This is convoluted, but it's working, although configuring such macros and producing little wrappers is not being ideal. If anyone could detail a simpler procedure I would be glad. Everything I tried ended up with duplicated symbols or non-visible declarations.