Go kodunu C'den arayabilirsiniz. Bu kafa karıştırıcı bir öneridir.
İşlem, bağlandığınız blog gönderisinde özetlenmiştir. Ama bunun nasıl yardımcı olmadığını görebiliyorum. İşte gereksiz bitleri olmayan kısa bir snippet. İşleri biraz daha netleştirmeli.
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)) )
}
Her şeyin çağrılma sırası aşağıdaki gibidir:
foo.MyAdd(a, b) ->
C.doAdd(a, b) ->
C.goCallbackHandler(a, b) ->
foo.goCallbackHandler(a, b)
Burada hatırlanması gereken anahtar, geri arama işlevinin //export
Git tarafındaki ve extern
C tarafındaki yorumla işaretlenmesi gerektiğidir . Bu, kullanmak istediğiniz geri aramaların paketinizin içinde tanımlanması gerektiği anlamına gelir.
Paketinizin bir kullanıcının özel bir geri arama işlevi sağlamasına izin vermek için, yukarıdakiyle aynı yaklaşımı kullanırız, ancak kullanıcının özel işleyicisini (yalnızca normal bir Go işlevi olan) C'ye iletilen bir parametre olarak tedarik ederiz. olarak void*
. Daha sonra paketimizdeki callbackhandler tarafından alınır ve çağrılır.
Şu anda üzerinde çalıştığım daha gelişmiş bir örnek kullanalım. Bu durumda, oldukça ağır bir görev gerçekleştiren bir C fonksiyonumuz var: Bir USB cihazından dosyaların listesini okur. Bu biraz zaman alabilir, bu yüzden uygulamamızın ilerlemesi konusunda bilgilendirilmesini istiyoruz. Bunu, programımızda tanımladığımız bir fonksiyon işaretçisini ileterek yapabiliriz. Sadece arandığında kullanıcıya bazı ilerleme bilgileri görüntüler. İyi bilinen bir imzası olduğundan, kendi türünü atayabiliriz:
type ProgressHandler func(current, total uint64, userdata interface{}) int
Bu işleyici, kullanıcının tutması gereken her şeyi tutabilecek bir arabirim {} değeri ile birlikte bazı ilerleme bilgisi (mevcut alınan dosya sayısı ve toplam dosya sayısı) alır.
Şimdi bu işleyiciyi kullanmamıza izin vermek için C ve Go tesisatını yazmamız gerekiyor. Neyse ki kütüphaneden çağırmak istediğim C fonksiyonu, kullanıcı tipinde bir yapı geçirmemizi sağlıyor void*
. Bu, tutmasını istediğimiz her şeyi tutabileceği, soru sormadığı ve onu Go dünyasına olduğu gibi geri getireceğimiz anlamına gelir. Tüm bu işleri yapmak için doğrudan Go'dan kütüphane işlevini çağırmıyoruz, ancak bunun için adlandıracağımız bir C sarmalayıcısı oluşturuyoruz goGetFiles()
. Bir kullanıcı veri nesnesiyle birlikte Go kütüphanemizi C kütüphanesine sağlayan bu sarmalayıcıdır.
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()
İşlevin, parametre olarak geri çağrılar için herhangi bir işlev işaretçisi almadığını unutmayın . Bunun yerine, kullanıcılarımızın sağladığı geri arama, hem bu işleyiciyi hem de kullanıcının kendi kullanıcı veri değerini tutan özel bir yapıda paketlenir. Bunu goGetFiles()
userdata parametresi olarak geçiriyoruz.
// 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 bağlarımız için bu kadar. Kullanıcının kodu artık çok basit:
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
}
Bunların hepsi olduğundan çok daha karmaşık görünüyor. Çağrı sırası önceki örneğimize göre değişmedi, ancak zincirin sonunda iki ekstra çağrı alıyoruz:
Sipariş aşağıdaki gibidir:
foo.GetFiles(....) ->
C.goGetFiles(...) ->
C.somelib_get_files(..) ->
C.goProgressCB(...) ->
foo.goProgressCB(...) ->
main.myProgress(...)