从C调用Go函数


150

我正在尝试创建一个用Go语言编写的静态对象,以与C程序(例如,内核模块)交互。

我已经找到了有关从Go调用C函数的文档,但是关于如何走另一条路却找不到很多。我发现这是可能的,但是很复杂。

这是我发现的:

有关C和Go之间的回调的博客文章

CGO文档

Golang邮件列表帖子

有任何人对此有经验吗?简而言之,我正在尝试创建一个完全用Go编写的PAM模块。


11
至少从不是从Go创建的线程中,您不能这样做。我为此烦恼了无数次,并且在修复此问题之前一直在Go中停止开发。
马特·乔纳

我听说有可能。有没有解决办法?
Beatgammit

Go使用不同的调用约定和分段堆栈。您也许可以将用gccgo编译的Go代码与C代码链接起来,但是我没有尝试过,因为我还没有在系统上构建gccgo。
mkb

。我想,现在使用痛饮,我希望......我还没有得到任何工作尚未虽然... ='(我贴在邮件列表中希望有人可怜我吧!
beatgammit

2
您可以从C 调用 Go代码,但目前还不能将Go运行时嵌入到C应用程序中,这是一个重要但细微的区别。
tylerl 2011年

Answers:


126

您可以从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)

这里要记住的关键是,回调函数必须//export在Go侧和externC侧都标记有注释。这意味着您要使用的任何回调都必须在包中定义。

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

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

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

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

现在,我们需要编写C和Go管道,以允许我们使用此处理程序。幸运的是,我希望从库中调用的C函数允许我们传递type的userdata结构void*。这意味着它可以容纳我们想要容纳的任何东西,不问任何问题,我们将其按原样带回到Go世界。为了使所有这些工作正常进行,我们没有直接从Go中调用库函数,而是为其创建了一个C包装器,将其命名为goGetFiles()。正是这个包装器将我们的Go回调以及一个userdata对象一起提供给了C库。

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值。我们将其goGetFiles()作为userdata参数传递。

// 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(...)

是的,我的确意识到当单独的线程发挥作用时,所有这些都可能在我们的脸上炸毁。特别是那些不是由Go创建的。不幸的是,事情就是这样。
2011年

17
这是一个很好的答案,而且很彻底。它没有直接回答问题,而是因为没有答案。根据一些消息来源,切入点必须是Go,不能是C。我将其标记为正确,因为这确实为我清除了东西。谢谢!
Beatgammit

@jimt与垃圾收集器如何集成?具体来说,何时收集私有progressRequest实例(如果有)?(是Go的新手,因此是unsafe.Pointer)。此外,像SQLite3这样的API怎么处理呢?它需要一个void *用户数据,但也为用户数据提供了可选的“删除程序”功能?可以使用它与GC交互,告诉它“如果Go边不再引用它,现在可以回收该用户数据了?”。
ddevienne

6
作为围棋1.5的有来自C.见这个问题打电话去更好的支持,如果你正在寻找演示了一个简单的技术答案:stackoverflow.com/questions/32215509/...
加布里埃尔南航

2
从Go 1.6开始,此方法不起作用,它破坏了“调用返回后C代码可能不保留Go指针的副本”。规则并在运行时发出“恐慌:运行时错误:cgo参数将Go指针指向Go指针”错误
卡巴斯基,2016年

56

如果使用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);
}

生成文件

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

当我使用字符串执行代码时,go package main func Add(a, b string) int { return a + b }出现错误“ undefined _go_string_plus”
TruongSinh 2014年

2
TruongSinh,您可能想使用cgogo代替gccgo。参见golang.org/cmd/cgo。话虽如此,完全有可能在.go文件中使用“字符串”类型,并将.c文件更改为包含__go_string_plus函数。这项工作:ix.io/dZB
Alexander


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.