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 //export
nhận xét ở bên Go và như extern
bê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(...)