Chức năng gọi đi từ C


150

Tôi đang cố gắng tạo một đối tượng tĩnh được viết trong giao diện Chuyển đến với một chương trình C (giả sử, một mô-đun hạt nhân hoặc một cái gì đó).

Tôi đã tìm thấy tài liệu về cách gọi các hàm C từ Go, nhưng tôi không tìm thấy nhiều về cách đi theo cách khác. Những gì tôi đã tìm thấy là nó có thể, nhưng phức tạp.

Đây là những gì tôi tìm thấy:

Bài đăng trên blog về cuộc gọi lại giữa C và Go

Tài liệu hướng dẫn

Golang gửi danh sách gửi thư

Có ai có kinh nghiệm với điều này? Nói tóm lại, tôi đang cố gắng tạo một mô-đun PAM được viết hoàn toàn bằng Go.


11
Bạn không thể, ít nhất là từ các chủ đề không được tạo từ Go. Tôi đã nổi giận về điều này nhiều lần và đã ngừng phát triển trong Go cho đến khi điều này được khắc phục.
Matt Joiner

Tôi nghe nói rằng nó có thể. Có giải pháp nào không?
beatgammit

Go sử dụng một quy ước gọi khác nhau và ngăn xếp phân đoạn. Bạn có thể liên kết mã Go được biên dịch bằng gccgo với mã C, nhưng tôi chưa thử điều này vì tôi chưa có gccgo để xây dựng trên hệ thống của mình.
mkb

Bây giờ tôi đang thử nó bằng SWIG và tôi hy vọng ... Tôi chưa có gì để làm việc cả ... = '(Tôi đã đăng trên danh sách gửi thư. Hy vọng ai đó thương xót tôi.
beatgammit

2
Bạn có thể gọi mã Go từ C, nhưng hiện tại bạn không thể nhúng thời gian chạy Go vào ứng dụng C, đây là một sự khác biệt quan trọng nhưng tinh tế.
tylerl

Câu trả lời:


126

Bạn có thể gọi mã Go từ C. mặc dù đó là một đề xuất khó hiểu.

Quá trình này được nêu trong bài đăng blog bạn liên kết đến. Nhưng tôi có thể thấy nó không hữu ích lắm. Đây là một đoạn ngắn mà không có bất kỳ bit không cần thiết. Nó sẽ làm cho mọi thứ rõ ràng hơn một chút.

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

Thứ tự mà mọi thứ được gọi là như sau:

foo.MyAdd(a, b) ->
  C.doAdd(a, b) ->
    C.goCallbackHandler(a, b) ->
      foo.goCallbackHandler(a, b)

Chìa khóa cần nhớ ở đây là chức năng gọi lại phải được đánh dấu bằng //exportnhận xét ở bên Go và như externbên C. Điều này có nghĩa là bất kỳ cuộc gọi lại nào bạn muốn sử dụng, phải được xác định trong gói của bạn.

Để cho phép người dùng gói của bạn cung cấp chức năng gọi lại tùy chỉnh, chúng tôi sử dụng cách tiếp cận chính xác như trên, nhưng chúng tôi cung cấp trình xử lý tùy chỉnh của người dùng (chỉ là chức năng Go thông thường) như một tham số được truyền vào C bên như void*. Sau đó, nó được nhận bởi người gọi lại trong gói của chúng tôi và được gọi.

Hãy sử dụng một ví dụ nâng cao hơn mà tôi hiện đang làm việc. Trong trường hợp này, chúng ta có chức năng C thực hiện một nhiệm vụ khá nặng: Nó đọc danh sách các tệp từ thiết bị USB. Điều này có thể mất một lúc, vì vậy chúng tôi muốn ứng dụng của chúng tôi được thông báo về tiến trình của nó. Chúng ta có thể làm điều này bằng cách chuyển vào một con trỏ hàm mà chúng ta đã xác định trong chương trình của mình. Nó chỉ đơn giản là hiển thị một số thông tin tiến trình cho người dùng bất cứ khi nào nó được gọi. Vì nó có chữ ký nổi tiếng, chúng ta có thể gán cho nó loại riêng:

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

Trình xử lý này lấy một số thông tin tiến trình (số tệp hiện tại nhận được và tổng số tệp) cùng với giá trị {} giao diện có thể chứa bất cứ thứ gì người dùng cần để giữ.

Bây giờ chúng ta cần viết hệ thống ống nước C và Go để cho phép chúng ta sử dụng trình xử lý này. May mắn thay, hàm C tôi muốn gọi từ thư viện cho phép chúng ta chuyển qua một cấu trúc kiểu người dùng void*. Điều này có nghĩa là nó có thể giữ bất cứ thứ gì chúng tôi muốn giữ, không có câu hỏi nào và chúng tôi sẽ đưa nó trở lại thế giới Go như hiện tại. Để thực hiện tất cả công việc này, chúng tôi không gọi hàm thư viện từ Go trực tiếp, nhưng chúng tôi tạo một trình bao bọc C cho nó mà chúng tôi sẽ đặt tên goGetFiles(). Chính trình bao bọc này thực sự cung cấp cuộc gọi lại Go của chúng tôi đến thư viện C, cùng với một đối tượng 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"

Lưu ý rằng goGetFiles()hàm không lấy bất kỳ con trỏ hàm nào cho các cuộc gọi lại làm tham số. Thay vào đó, cuộc gọi lại mà người dùng của chúng tôi đã cung cấp được đóng gói trong một cấu trúc tùy chỉnh chứa cả trình xử lý đó và giá trị userdata của chính người dùng. Chúng tôi chuyển cái này thành goGetFiles()tham số 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 ))
}

Đó là cho các ràng buộc C của chúng tôi. Mã của người dùng bây giờ rất đơn giản:

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
}

Tất cả điều này có vẻ phức tạp hơn nhiều so với nó. Thứ tự cuộc gọi đã không thay đổi so với ví dụ trước của chúng tôi, nhưng chúng tôi nhận được hai cuộc gọi thêm vào cuối chuỗi:

Thứ tự như sau:

foo.GetFiles(....) ->
  C.goGetFiles(...) ->
    C.somelib_get_files(..) ->
      C.goProgressCB(...) ->
        foo.goProgressCB(...) ->
           main.myProgress(...)

Vâng, tôi nhận ra rằng tất cả những điều này có thể nổ tung trong khuôn mặt của chúng tôi khi các chủ đề riêng biệt xuất hiện. Cụ thể là những người không được tạo bởi Go. Thật không may, đó là cách mọi thứ ở thời điểm này.
jimt

17
Đây là một câu trả lời thực sự tốt, và kỹ lưỡng. Nó không trả lời trực tiếp câu hỏi, nhưng đó là vì không có câu trả lời. Theo một số nguồn tin, điểm vào phải là Go và không thể là C. Tôi đánh dấu điều này là chính xác bởi vì điều này thực sự đã xóa mọi thứ đối với tôi. Cảm ơn!
beatgammit

@jimt Làm thế nào để tích hợp với trình thu gom rác? Cụ thể, khi nào thì cá thể tiến trình riêng được thu thập, nếu có bao giờ? (Mới đi, và do đó không an toàn. Con trỏ). Ngoài ra, những gì về API như SQLite3 có một void * userdata, nhưng cũng là một chức năng "deleter" tùy chọn cho userdata? Điều đó có thể được sử dụng để tương tác với GC không, để nói với nó "bây giờ bạn có thể lấy lại userdata đó không, miễn là phía Go không tham chiếu nó nữa?".
ddevienne

6
Kể từ Go 1.5, có sự hỗ trợ tốt hơn để gọi Go từ C. Xem câu hỏi này nếu bạn đang tìm kiếm một câu trả lời thể hiện một kỹ thuật đơn giản hơn: stackoverflow.com/questions/32215509/
Gabriel Southern

2
Kể từ Go 1.6, cách tiếp cận này không hoạt động, nó phá vỡ "Mã C có thể không giữ một bản sao của con trỏ Go sau khi cuộc gọi trở lại." quy tắc và đưa ra lỗi "hoảng loạn: thời gian chạy: đối số cgo có lỗi Con trỏ đi tới con trỏ" trong thời gian chạy
kaspersky

56

Nó không phải là một đề xuất khó hiểu nếu bạn sử dụng gccgo. Điều này hoạt động ở đây:

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

Khi tôi đi mã làm sth với chuỗi go package main func Add(a, b string) int { return a + b }tôi gặp lỗi "không xác định _go_opes_plus"
TruongSinh

2
TruongSinh, có lẽ bạn muốn sử dụng cgogothay vì gccgo. Xem golang.org/cmd/cgo . Khi nói điều đó, hoàn toàn có thể sử dụng loại "chuỗi" trong tệp .go và thay đổi tệp .c của bạn để bao gồm một __go_string_plushàm. Tác phẩm này: ix.io/dZB
Alexander


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.