2011-05-25 48 views
124

我正在尝试创建一个静态对象,用于与C程序(例如,内核模块或某个东西)进行接口转换。来自C的电话转到函数

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

以下是我发现:

Blog post about callbacks between C and Go

Cgo documentation

Golang mailing list post

有没有人有这方面的经验?总之,我试图创建一个完全用Go编写的PAM模块。

+10

您不能,至少不是从Go创建的线程。我经历了这么多次,并且在Go中停止开发,直到这个问题得到解决。 – 2011-05-25 16:08:46

+0

我听说这是可能的。有没有解决方案? – tjameson 2011-05-25 16:56:43

+0

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

回答

109

您可以从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评论和extern上标明C方。这意味着您希望使用的任何回调都必须在您的包中定义。

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

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

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

此处理需要一定的进展信息(接收的文件的当前文件的数量和总数量)以及接口{}值可以抓住用户需要的任何东西。

现在我们需要编写C和Go管道让我们使用这个处理程序。幸运的是,我希望从库调用的C函数允许我们传入void*类型的userdata结构。这意味着它可以拥有我们想要的任何东西,不会提出任何问题,我们会按原样将它恢复到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(...) 
+0

是的,我意识到当单独的线程发挥作用时,所有这些可能会炸毁我们的脸。特别是那些不是由Go创建的。不幸的是,这是事情的方式。 – jimt 2011-05-27 02:22:07

+13

这是一个非常好的答案,并且彻底。它不直接回答这个问题,但那是因为没有答案。根据几个消息来源,入口点必须是Go,不能是C.我将这个标记为正确的,因为这真的为我清除了一些东西。谢谢! – tjameson 2011-05-27 02:45:53

+0

@jimt与垃圾收集器如何集成?具体来说,何时收集私有progressRequest实例? (新的去,并因此不安全的指标)。 此外,API如SQLite3采取void * userdata,但也是一个可选的“删除”功能userdata?这可以用来与GC进行交互,告诉它“现在可以回收该用户数据,只要Go方不再引用它?”。 – ddevienne 2013-06-02 18:06:29

48

这不是一个令人困惑的命题,如果你使用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 
+0

当我去代码做与字符串 go 包主 func添加(a,b字符串)int { return a + b }'我得到错误“undefined _go_string_plus” – TruongSinh 2014-07-28 09:09:24

+2

TruongSinh,您可能想使用'cgo'和'go'而不是'gccgo'。请参阅http://golang.org/cmd/cgo/。如果这么说,完全有可能在.go文件中使用“string”类型,并改变.c文件以包含'__go_string_plus'函数。这工作:http://ix.io/dZB – Alexander 2014-08-22 13:25:30