Anda dapat memanggil kode Go dari C. itu adalah proposisi yang membingungkan.
Prosesnya diuraikan dalam posting blog yang Anda tautkan. Tapi saya bisa melihat bagaimana itu tidak terlalu membantu. Berikut ini cuplikan singkat tanpa bit yang tidak perlu. Seharusnya hal-hal sedikit lebih jelas.
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)) )
}
Urutan semua hal disebut adalah sebagai berikut:
foo.MyAdd(a, b) ->
C.doAdd(a, b) ->
C.goCallbackHandler(a, b) ->
foo.goCallbackHandler(a, b)
Kunci untuk diingat di sini adalah bahwa fungsi panggilan balik harus ditandai dengan //export
komentar di sisi Go dan seperti extern
di sisi C. Ini berarti bahwa setiap panggilan balik yang ingin Anda gunakan, harus ditentukan di dalam paket Anda.
Untuk memungkinkan pengguna paket Anda untuk menyediakan fungsi panggilan balik khusus, kami menggunakan pendekatan yang sama persis seperti di atas, tetapi kami menyediakan penangan kustom pengguna (yang hanya merupakan fungsi Go biasa) sebagai parameter yang diteruskan ke C sisi sebagai void*
. Ini kemudian diterima oleh penelepon balik dalam paket kami dan dipanggil.
Mari kita gunakan contoh yang lebih maju yang saat ini saya kerjakan. Dalam hal ini, kami memiliki fungsi C yang melakukan tugas yang cukup berat: Ini membaca daftar file dari perangkat USB. Ini bisa memakan waktu cukup lama, jadi kami ingin aplikasi kami diberitahu tentang perkembangannya. Kita dapat melakukan ini dengan mengirimkan pointer fungsi yang kita tetapkan dalam program kita. Ini hanya menampilkan beberapa info kemajuan kepada pengguna setiap kali dipanggil. Karena memiliki tanda tangan yang terkenal, kita dapat menetapkan jenisnya sendiri:
type ProgressHandler func(current, total uint64, userdata interface{}) int
Handler ini mengambil beberapa info kemajuan (jumlah file yang diterima saat ini dan jumlah total file) bersama dengan nilai antarmuka {} yang dapat menampung apa saja yang dibutuhkan pengguna untuk dipegang.
Sekarang kita perlu menulis pipa C dan Go untuk memungkinkan kita menggunakan penangan ini. Untungnya fungsi C yang ingin saya panggil dari pustaka memungkinkan kita untuk memasukkan data tipe userdata void*
. Ini berarti dapat menyimpan apa pun yang kita inginkan, tidak ada pertanyaan yang diajukan dan kita akan mendapatkannya kembali ke Dunia Go apa adanya. Untuk membuat semua ini berfungsi, kami tidak memanggil fungsi pustaka dari Go secara langsung, tetapi kami membuat pembungkus C untuk itu yang akan kami beri nama goGetFiles()
. Ini pembungkus ini yang benar-benar memasok panggilan balik Go kami ke perpustakaan C, bersama dengan objek data pengguna.
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"
Perhatikan bahwa goGetFiles()
fungsi tidak mengambil pointer fungsi untuk panggilan balik sebagai parameter. Alih-alih, panggilan balik yang diberikan pengguna kami dikemas dalam struct khusus yang berisi nilai data pengguna itu sendiri. Kami meneruskan ini goGetFiles()
sebagai parameter data pengguna.
// 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 ))
}
Itu saja untuk binding C kami. Kode pengguna sekarang sangat mudah:
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
}
Ini semua terlihat jauh lebih rumit daripada itu. Pesanan panggilan tidak berubah dibandingkan dengan contoh kami sebelumnya, tetapi kami mendapatkan dua panggilan tambahan di akhir rantai:
Urutannya adalah sebagai berikut:
foo.GetFiles(....) ->
C.goGetFiles(...) ->
C.somelib_get_files(..) ->
C.goProgressCB(...) ->
foo.goProgressCB(...) ->
main.myProgress(...)