从C调用Go功能?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (18)

我正尝试创建一个用Go编写的静态对象来与C程序(比如内核模块或某个东西)进行交互。

我已经找到了关于从Go调用C函数的文档,但我还没有找到很多关于如何去其他方面的文档。我发现这是可能的,但很复杂。

提问于
用户回答回答于

你可以从C中调用Go代码,但这是一个令人困惑的提议。

该流程在你链接到的博客文章中进行了概述。但我可以看到这并不是非常有用。这是一个没有任何不必要的位的短片段。它应该让事情更清楚一点。

package foo

// extern int goCallbackHandler(int, int);
//
// static int doAdd(int a, int b) {
//     return goCallbackHandler(a, b);
// }
import "C"

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

// This is the public function, callable from outside this package.
// It forwards the parameters to C.doAdd(), which in turn forwards
// them back to goCallbackHandler(). This one performs the addition
// and yields the result.
func MyAdd(a, b int) int {
   return int( C.doAdd( C.int(a), C.int(b)) )
}

所有内容的调用顺序如下:

foo.MyAdd(a, b) ->
  C.doAdd(a, b) ->
    C.goCallbackHandler(a, b) ->
      foo.goCallbackHandler(a, b)

这里要记住的关键是一个回调函数必须标有//exportGo侧的注释和C侧的注释extern。这意味着您希望使用的任何回调函数都必须在包中定义。

为了让你的包的用户提供一个自定义的回调函数,我们使用与上面完全相同的方法,但是我们提供用户的自定义处理函数(这只是一个常规的Go函数)作为参数传递给C方如void*。然后由我们的包中的回调处理程序接收并调用它。

让我们使用我目前正在使用的更高级的示例。在这种情况下,我们有一个C函数执行相当繁重的任务:它从USB设备读取文件列表。这可能需要一段时间,所以我们希望我们的应用程序被通知其进展。我们可以通过传递我们在程序中定义的函数指针来实现这一点。它只是在被调用时向用户显示一些进度信息。由于它有一个众所周知的签名,我们可以将它分配给它自己的类型:

type ProgressHandler func(current, total uint64, userdata interface{}) int

此处理程序会获取一些进度信息(当前接收的文件数量和文件总数)以及可以容纳用户需要的任何内容的接口{}值。

现在我们需要编写C和Go管道以允许我们使用这个处理程序。幸运的是,我希望从库调用的C函数允许我们传入一个类型的userdata结构体void*。这意味着它可以拥有我们想要的任何东西,不会提出任何问题,我们会按原样将它恢复到Go世界。为了完成所有这些工作,我们不直接从Go调用库函数,但是我们将为它创建一个C封装器,我们将命名它goGetFiles()。正是这个包装实际上提供了我们的Go回调函数以及一个userdata对象。

package foo

// #include <somelib.h>
// extern int goProgressCB(uint64_t current, uint64_t total, void* userdata);
// 
// static int goGetFiles(some_t* handle, void* userdata) {
//    return somelib_get_files(handle, goProgressCB, userdata);
// }
import "C"
import "unsafe"

请注意,goGetFiles()函数不会将回调函数指针作为参数。相反,我们用户提供的回调函数被封装在一个自定义结构中,该结构同时包含该处理程序和用户自己的userdata值。我们把它作为userdata参数传入goGetFiles()

// This defines the signature of our user's progress handler,
type ProgressHandler func(current, total uint64, userdata interface{}) int 

// This is an internal type which will pack the users callback function and userdata.
// It is an instance of this type that we will actually be sending to the C code.
type progressRequest struct {
   f ProgressHandler  // The user's function pointer
   d interface{}      // The user's userdata.
}

//export goProgressCB
func goProgressCB(current, total C.uint64_t, userdata unsafe.Pointer) C.int {
    // This is the function called from the C world by our expensive 
    // C.somelib_get_files() function. The userdata value contains an instance
    // of *progressRequest, We unpack it and use it's values to call the
    // actual function that our user supplied.
    req := (*progressRequest)(userdata)

    // Call req.f with our parameters and the user's own userdata value.
    return C.int( req.f( uint64(current), uint64(total), req.d ) )
}

// This is our public function, which is called by the user and
// takes a handle to something our C lib needs, a function pointer
// and optionally some user defined data structure. Whatever it may be.
func GetFiles(h *Handle, pf ProgressFunc, userdata interface{}) int {
   // Instead of calling the external C library directly, we call our C wrapper.
   // We pass it the handle and an instance of progressRequest.

   req := unsafe.Pointer(&progressequest{ pf, userdata })
   return int(C.goGetFiles( (*C.some_t)(h), req ))
}

这就是我们的C绑定。用户的代码现在非常简单:

package main

import (
    "foo"
    "fmt"
)

func main() {
    handle := SomeInitStuff()

    // We call GetFiles. Pass it our progress handler and some
    // arbitrary userdata (could just as well be nil).
    ret := foo.GetFiles( handle, myProgress, "Callbacks rock!" )

    ....
}

// This is our progress handler. Do something useful like display.
// progress percentage.
func myProgress(current, total uint64, userdata interface{}) int {
    fc := float64(current)
    ft := float64(total) * 0.01

    // print how far along we are.
    // eg: 500 / 1000 (50.00%)
    // For good measure, prefix it with our userdata value, which
    // we supplied as "Callbacks rock!".
    fmt.Printf("%s: %d / %d (%3.2f%%)\n", userdata.(string), current, total, fc / ft)
    return 0
}

这看起来比现在复杂得多。与我们前面的例子相比,呼叫顺序并没有改变,但我们在链的末尾得到两个额外的呼叫:

顺序如下:

foo.GetFiles(....) ->
  C.goGetFiles(...) ->
    C.somelib_get_files(..) ->
      C.goProgressCB(...) ->
        foo.goProgressCB(...) ->
           main.myProgress(...)
用户回答回答于

如果你使用gccgo,这不是一个令人困惑的命题。这在这里工作:

foo.go

package main

func Add(a, b int) int {
    return a + b
}

bar.c

#include <stdio.h>

extern int go_add(int, int) __asm__ ("example.main.Add");

int main() {
  int x = go_add(2, 3);
  printf("Result: %d\n", x);
}

Makefile文件

all: main

main: foo.o bar.c
    gcc foo.o bar.c -o main

foo.o: foo.go
    gccgo -c foo.go -o foo.o -fgo-prefix=example

clean:
    rm -f main *.o

扫码关注云+社区