从C中调用Go函数

huangapple go评论113阅读模式
英文:

Call Go functions from C

问题

我正在尝试创建一个用Go语言编写的静态对象,与C程序(比如内核模块或其他)进行接口交互。

我已经找到了关于如何从Go调用C函数的文档,但是关于如何反过来的资料并不多。我找到的是这是可能的,但是比较复杂。

以下是我找到的资料:

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

Cgo文档

Golang邮件列表帖子

有没有人有相关经验?简而言之,我正在尝试创建一个完全用Go语言编写的PAM模块。

英文:

I am trying to create a static object written in Go to interface with a C program (say, a kernel module or something).

I have found documentation on calling C functions from Go, but I haven't found much on how to go the other way. What I've found is that it's possible, but complicated.

Here is what I found:

Blog post about callbacks between C and Go

Cgo documentation

Golang mailing list post

Does anyone have experience with this? In short, I'm trying to create a PAM module written entirely in Go.

答案1

得分: 139

你可以从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
}

// 这是一个公共函数,可以从包外部调用。
// 它将参数转发给C.doAdd(),然后C.doAdd()再转发给goCallbackHandler()。
// 这个函数执行加法并返回结果。
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()。正是这个包装器实际上将我们的Go回调与C库一起提供,以及一个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()

// 这定义了用户的进度处理程序的签名
type ProgressHandler func(current, total uint64, userdata interface{}) int 

// 这是一个内部类型,它将打包用户的回调函数和userdata。
// 实际上,我们将发送给C代码的是这个类型的实例。
type progressRequest struct {
   f ProgressHandler  // 用户的函数指针
   d interface{}      // 用户的userdata
}

//export goProgressCB
func goProgressCB(current, total C.uint64_t, userdata unsafe.Pointer) C.int {
    // 这是我们的昂贵的C.somelib_get_files()函数在C世界中调用的函数。
    // userdata值包含*progressRequest的实例,我们解包它并使用它的值调用用户提供的实际函数。
    req := (*progressRequest)(userdata)
    
    // 使用我们的参数和用户自己的userdata值调用req.f。
    return C.int(req.f(uint64(current), uint64(total), req.d))
}

// 这是我们的公共函数,由用户调用,
// 它接受一个我们的C库需要的句柄、一个函数指针和可选的用户定义的数据结构,无论它是什么。
func GetFiles(h *Handle, pf ProgressFunc, userdata interface{}) int {
   // 我们不直接调用外部C库,而是调用我们的C包装器。
   // 我们将句柄和progressRequest的实例传递给它。

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

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

package main

import (
    "foo"
    "fmt"
)

func main() {
    handle := SomeInitStuff()
    
    // 我们调用GetFiles。将进度处理程序和一些任意的userdata传递给它(也可以是nil)。
    ret := foo.GetFiles(handle, myProgress, "Callbacks rock!")

    ....
}

// 这是我们的进度处理程序。做一些有用的事情,比如显示进度百分比。
func myProgress(current, total uint64, userdata interface{}) int {
    fc := float64(current)
    ft := float64(total) * 0.01

    // 打印我们的进度。
    // 例如:500 / 1000 (50.00%)
    // 为了保险起见,我们在前面加上我们提供的userdata值,即"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(...)
英文:

You can call the Go code from C. It is a confusing proposition, though.

The process is outlined in the blog post you linked to. But I can see how that isn't very helpful. Here is a short snippet without any unnecessary bits. It should make things a little clearer.

package foo

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

//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)) )
}

The order in which everything is called is as follows:

foo.MyAdd(a, b) -&gt;
  C.doAdd(a, b) -&gt;
    C.goCallbackHandler(a, b) -&gt;
      foo.goCallbackHandler(a, b)

The key to remember here is that a callback function must be marked with the //export comment on the Go side and as extern on the C side. This means that any callback you wish to use, must be defined inside your package.

In order to allow a user of your package to supply a custom callback function, we use the exact same approach as above, but we supply the user's custom handler (which is just a regular Go function) as a parameter that is passed onto the C side as void*. It is then received by the callbackhandler in our package and called.

Let's use a more advanced example I am currently working with. In this case, we have a C function that performs a pretty heavy task: It reads a list of files from a USB device. This can take a while, so we want our app to be notified of its progress. We can do this by passing in a function pointer that we defined in our program. It simply displays some progress info to the user whenever it gets called. Since it has a well known signature, we can assign it its own type:

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

This handler takes some progress info (current number of files received and total number of files) along with an interface{} value which can hold anything the user needs it to hold.

Now we need to write the C and Go plumbing to allow us to use this handler. Luckily the C function I wish to call from the library allows us to pass in a userdata struct of type void*. This means it can hold whatever we want it to hold, no questions asked and we will get it back into the Go world as-is. To make all this work, we do not call the library function from Go directly, but we create a C wrapper for it which we will name goGetFiles(). It is this wrapper that actually supplies our Go callback to the C library, along with a userdata object.

package foo

// #include &lt;somelib.h&gt;
// 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 &quot;C&quot;
import &quot;unsafe&quot;

Note that the goGetFiles() function does not take any function pointers for callbacks as parameters. Instead, the callback that our user has supplied is packed in a custom struct that holds both that handler and the user's own userdata value. We pass this into goGetFiles() as the userdata parameter.

// This defines the signature of our user&#39;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&#39;s function pointer
   d interface{}      // The user&#39;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&#39;s values to call the
    // actual function that our user supplied.
    req := (*progressRequest)(userdata)
    
    // Call req.f with our parameters and the user&#39;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(&amp;progressequest{ pf, userdata })
   return int(C.goGetFiles( (*C.some_t)(h), req ))
}

That's it for our C bindings. The user's code is now very straight forward:

package main

import (
    &quot;foo&quot;
    &quot;fmt&quot;
)

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, &quot;Callbacks rock!&quot; )

    ....
}

// 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 &quot;Callbacks rock!&quot;.
    fmt.Printf(&quot;%s: %d / %d (%3.2f%%)\n&quot;, userdata.(string), current, total, fc / ft)
    return 0
}

This all looks a lot more complicated than it is. The call order has not changed as opposed to our previous example, but we get two extra calls at the end of the chain:

The order is as follows:

foo.GetFiles(....) -&gt;
  C.goGetFiles(...) -&gt;
    C.somelib_get_files(..) -&gt;
      C.goProgressCB(...) -&gt;
        foo.goProgressCB(...) -&gt;
           main.myProgress(...)

答案2

得分: 61

这是一个使用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
英文:

It is not a confusing proposition if you use gccgo. This works here:

foo.go

package main

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

bar.c

#include &lt;stdio.h&gt;

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

int main() {
  int x = go_add(2, 3);
  printf(&quot;Result: %d\n&quot;, 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

答案3

得分: 17

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

我之前提出的这个SO问题再次讨论了1.5版本增加的功能。

https://stackoverflow.com/questions/32215509/using-go-code-in-an-existing-c-project

英文:

The answer has changed with the release of Go 1.5

This SO question that I asked some time ago addresses the issue again in light of the 1.5 added capabilities

https://stackoverflow.com/questions/32215509/using-go-code-in-an-existing-c-project

答案4

得分: 3

据我所知,这是不可能的:

> 注意:如果你使用exports,就不能在导言部分定义任何C函数。

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

英文:

As far as I am concerned it isn't possible:

> Note: you can't define any C functions in preamble if you're using
> exports.

source: https://github.com/golang/go/wiki/cgo

huangapple
  • 本文由 发表于 2011年5月25日 22:02:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/6125683.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定