ChatGPT解决这个技术问题 Extra ChatGPT

从 C 调用 Go 函数

我正在尝试创建一个用 Go 编写的静态对象,以与 C 程序(例如,内核模块或其他东西)接口。

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

这是我发现的:

Blog post about callbacks between C and Go

Cgo documentation

Golang mailing list post

这个事情谁有经验?简而言之,我正在尝试创建一个完全用 Go 编写的 PAM 模块。

你不能,至少不是从 Go 创建的线程。我已经为此大发雷霆了无数次,并在解决此问题之前停止了 Go 开发。
我听说这是可能的。没有解决办法吗?
Go 使用不同的调用约定和分段堆栈。您可能可以将使用 gccgo 编译的 Go 代码与 C 代码链接,但我没有尝试过这个,因为我还没有让 gccgo 在我的系统上构建。
我现在正在使用 SWIG 尝试它,我充满希望......虽然我还没有任何工作...... ='(我在邮件列表上发布。希望有人能怜悯我。
您可以从 C 调用 Go 代码,但目前您不能将 Go 运行时嵌入到 C 应用程序中,这是一个重要但微妙的区别。

A
Amin Shojaei

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

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

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

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

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

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

现在我们需要编写 C 和 Go 管道以允许我们使用这个处理程序。幸运的是,我希望从库中调用的 C 函数允许我们传入 void* 类型的 userdata 结构。这意味着它可以容纳我们想要容纳的任何东西,无需提出任何问题,我们将按原样将其带回 Go 世界。为了完成所有这些工作,我们不直接从 Go 调用库函数,而是为其创建一个 C 包装器,我们将其命名为 goGetFiles()。正是这个包装器实际上为 C 库提供了我们的 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(...)

是的,我确实意识到,当单独的线程开始发挥作用时,所有这一切都可能在我们的脸上炸开。特别是那些不是由 Go 创建的。不幸的是,目前情况就是这样。
这是一个非常好的答案,而且很彻底。它没有直接回答问题,但那是因为没有答案。根据几个消息来源,入口点必须是 Go,不能是 C。我将其标记为正确,因为这确实为我清除了问题。谢谢!
@jimt 它如何与垃圾收集器集成?具体来说,何时收集私有 progressRequest 实例(如果有)? (对于 Go 来说是新手,因此对于 unsafe.Pointer 来说也是如此)。另外,像 SQLite3 这样带有 void* 用户数据的 API 以及用户数据的可选“删除器”函数呢?可以用来与 GC 交互,告诉它“现在可以回收该用户数据,只要 Go 端不再引用它吗?”。
从 Go 1.5 开始,对从 C 调用 Go 提供了更好的支持。如果您正在寻找演示更简单技术的答案,请参阅此问题:stackoverflow.com/questions/32215509/…
从 Go 1.6 开始,这种方法不起作用,它破坏了“C 代码在调用返回后可能不会保留 Go 指针的副本”。规则并在运行时发出“恐慌:运行时错误:cgo 参数有 Go 指针指向 Go 指针”错误
A
Alexander

如果您使用 gccgo,这不是一个令人困惑的提议。这在这里有效:

foo.go

package main

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

酒吧.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,您可能想要使用 cgogo 而不是 gccgo。请参阅golang.org/cmd/cgo。话虽如此,完全可以在 .go 文件中使用“字符串”类型并更改 .c 文件以包含 __go_string_plus 函数。这有效:ix.io/dZB
C
Community

答案随着 Go 1.5 的发布而改变

我前段时间问的这个 SO 问题根据 1.5 添加的功能再次解决了这个问题

Using Go code in an existing C project


R
Rafael Ribeiro

就我而言,这是不可能的:

注意:如果您使用导出,则不能在序言中定义任何 C 函数。

来源:https://github.com/golang/go/wiki/cgo


关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅