我试图创建一个用C程序(比如内核模块之类)编写的Go-to接口的静态对象.

我已经找到了从Go调用C函数的文档,但我还没有找到多少关于如何从另一个方向调用C函数的文档.我发现这是可能的,但很复杂.

以下是我的发现:

Blog post about callbacks between C and Go

Cgo documentation

Golang mailing list post

有人有这方面的经验吗?简而言之,我试图创建一个完全用Go编写的PAM模块.

推荐答案

你可以从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函数)作为参数,作为void*传递到C端.然后,回调处理程序在我们的包中接收并调用它.

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

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

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

现在,我们需要编写C并执行管道操作,以允许我们使用此处理程序.幸运的是,我希望从库中调用的C函数允许我们传入类型为void*的userdata struct .这意味着它可以容纳我们想让它容纳的任何东西,没有任何问题,我们将把它带回围棋世界.要实现所有这些功能,我们不会直接从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()函数不将回调的任何函数指针作为参数.相反,我们的用户提供的回调被打包在一个自定义 struct 中,该 struct 同时保存该处理程序和用户自己的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(...)

C++相关问答推荐

函数指针始终为零,但在解除引用和调用时有效

如何启用ss(另一个调查套接字的实用程序)来查看Linux主机上加入的多播组IP地址?

如何在C中只使用一个带双方括号([i][j])访问语法的malloc来分配动态大小的2d数组?

va_copy的使用是未定义的行为吗?

为什么复合文字(C99)的返回会生成更多的汇编代码?

C:二进制搜索和二进制插入

将指针作为参数传递给函数

减法运算结果的平方的最快方法?

#If指令中未定义宏?

致命:ThreadSaniizer:在Linux内核6.6+上运行时意外的内存映射

为什么我可以在GCC的标签后声明变量,但不能声明Clang?

C";中的ANN运行时判断失败#2-变量outputLayer;周围的堆栈已损坏.运行后出错

静态初始化顺序失败是否适用于C语言?

使用C++中的字符串初始化 struct 时,从‘char*’初始化‘char’使指针变为整数,而不进行强制转换

RISC-V GCC编译器错误编译ASM代码

如何在Rust中处理C的longjmp情况?

如何用用户输入的多个字符串填充数组?

为什么需要struct in_addr

使用 SDL2 的 C 程序中的内存泄漏

c 函数指针,另一种语法