2011-05-25 23 views
124

C programında bir arabirim ile Go (Git, bir çekirdek modül veya bir şey) ile yazılmış statik bir nesne oluşturmaya çalışıyorum.Call Go işlevleri C

Go işlevlerinin C işlevlerini arama ile ilgili belgeleri buldum, ancak başka yoldan nasıl gidileceğine dair fazla bir şey bulamadım. Bulduğum şey bunun mümkün, ama karmaşık olmasıdır. İşte

ne buldum:

Blog post about callbacks between C and Go

Cgo documentation

Golang mailing list post

herkes bu deneyimi var mı

? Kısacası, tamamen Go’da yazılmış bir PAM modülü oluşturmaya çalışıyorum.

+10

En azından Go tarafından oluşturulmamış iş parçacıklarından yapamazsınız. Bu sayısız zamandan ötürü öfkelendim ve bu düzeltilene kadar Go'da gelişmeyi kestim. –

+0

Bunun mümkün olduğunu duydum. Çözüm yok mu? – tjameson

+0

Go, farklı bir çağrı kuralı ve bölümlere ayrılmış yığınlar kullanır. Gccgo ile derlenmiş Go kodunu C koduyla ilişkilendirebilirsiniz, ancak bu sistemi denemedim, çünkü gccgo'yu sistemimde oluşturmaya çalışmadım. – mkb

cevap

109

Go kodunu C'den arayabilirsiniz. Yine de kafa karıştırıcı bir önermedir.

İşlem, bağlantı kurduğunuz blog yayında özetlenmiştir. Ama bunun nasıl yardımcı olmadığını görebiliyorum. İşte gereksiz bitler olmadan kısa bir snippet. İşleri biraz daha netleştirmeli. aşağıdaki gibi

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şey denir sıradır:

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

burada unutulmaması gereken önemli bir geri çağırma işlevi Git tarafında //export yorum ile ve üzerinde extern olarak işaretlenmesi gerekir ki C tarafı. Bu, kullanmak istediğiniz herhangi bir geri dönüşün paketinizin içinde tanımlanması gerektiği anlamına gelir.

Paketinizin bir kullanıcısının özel bir geri arama işlevi sağlamasına izin vermek için, yukarıdakiyle tam olarak aynı yaklaşımı kullanırız, ancak kullanıcının özel işleyicisini (yalnızca normal bir Go işlevi olan) bir parametre olarak sağlarız. C tarafına void* olarak geçirilmiştir. Daha sonra paketteki callbackhandler tarafından alınır ve aranı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 fonksiyonuna sahibiz: Bir USB cihazındaki dosyaların bir listesini okur. Bu biraz zaman alabilir, bu nedenle uygulamanızın ilerlemesini bildirmesini istiyoruz. Bunu programımızda tanımladığımız bir işlev işaretçisini kullanarak yapabiliriz. Sadece çağrıldığında kullanıcıya bazı ilerleme bilgileri gösterir. bu bilinen bir imzası vardır yana, bunu kendi türünü atayabilirsiniz:

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

Bu işleyici bir arayüz {} değeri ile birlikte bazı ilerlemeler bilgi (dosya mevcut sayısı alınan ve dosyaların toplam sayısı) aldığı can Kullanıcının tutması gereken herhangi bir şeyi tutmak.

Şimdi bu işleyiciyi kullanmamıza izin vermek için C ve Go tesisatını yazmamız gerekiyor. Neyse ki kütüphaneden aramak istediğim C işlevi, void* türünde bir userdata yapısında geçiş yapmamıza izin veriyor. Bu, onu tutmak istediğimiz her şeyi tutabileceği, hiçbir soru sorulmadığı ve Go dünyasında olduğu gibi geri alabileceğimiz anlamına gelir. Tüm bu çalışmaları yapmak için doğrudan Go kütüphanesi işlevini çağırmıyoruz, ancak bunun için goGetFiles() adını vereceğimiz bir C sarıcı oluşturuyoruz. Aslında, Go geri bildirimini C kütüphanesine ve bir userdata nesnesiyle birlikte sunan bu sarmalayıcıdır.goGetFiles() fonksiyon parametreleri olarak geri aramalar için herhangi bir fonksiyon işaretçileri sürmüyor

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" 

Not. Bunun yerine, kullanıcımızın sağladığı geri arama, hem bu işleyiciyi hem de kullanıcının kendi kullanıcı veri değerini bulunduran özel bir yapıda paketlenmiştir. Bunu userdata parametresi olarak goGetFiles()'a aktarıyoruz.

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

Bu bizim C bağlamaları için geçerlidir. Kullanıcının kod çok yalındır artık şudur:

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 o çok daha karmaşık görünüyor. Önceki örneğe aksine çağrı sırası değişmedi, ama biz zincirin ucunda iki ekstra aramaları almak:

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

Evet, fark ettim ki, her şey ayrı iş parçacıkları devreye girdiğinde yüzümüzde patlayabilir. Özellikle Go tarafından yaratılmamış olanlar. Ne yazık ki, bu noktada şeylerin yolu budur. – jimt

+13

Bu gerçekten iyi bir cevap ve eksiksiz. Bu doğrudan soruyu cevaplamıyor, ama bunun sebebi cevabı yok. Çeşitli kaynaklara göre, giriş noktası Git olmalı ve C olamaz. Bunu doğru olarak işaretliyorum çünkü bu gerçekten benim için temizlenmiş şeyler. Teşekkürler! – tjameson

+0

@jimt Bu çöp toplayıcı ile nasıl entegre olur? Spesifik olarak, özel progressRequest örneği ne zaman toplanır? (Gitmek için Yeni ve böylece güvensiz.Pointer). Ayrıca, nasıl bir void * userdata, ancak aynı zamanda userdata için isteğe bağlı bir "deleter" işlevini alan SQLite3 API? GC ile etkileşimde bulunmak için, “artık tarafa başvurmadığı sürece artık kullanıcı verisini geri almak için sorun değil” diyebiliriz. – ddevienne

48

Bu kafa karıştırıcı bir önerme eğer değil:

sırası aşağıdaki gibidir gccgo kullanıyorsun. Bu burada çalışıyor:

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 Bildiğim kadarıyla kulüpler ilgili olarak

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

kod 'paketine git paket ana int (1) return a + b } 'Ben – TruongSinh

+2

TruongSinh, muhtemelen yerine gccgo'' arasında go' 'cgo' ve' kullanmak istediğiniz hata "tanımlanmamış _go_string_plus" olsun. Http://golang.org/cmd/cgo/ sayfasına bakın. Bu söylendiğinde, .go dosyasındaki "string" türünü kullanmak ve '__go_string_plus' işlevini içerecek şekilde .c dosyanızı değiştirmek tamamen mümkündür. Bu çalışır: http://ix.io/dZB – Alexander