C'den Call Go işlevleri


150

Git bir C programı (diyelim ki, bir çekirdek modülü ya da bir şey) ile arayüz yazılı bir statik nesne oluşturmaya çalışıyorum.

Go'dan C işlevlerini çağırmayla ilgili belgeler buldum, ancak diğer yöne nasıl gidileceğiyle ilgili pek bir şey bulamadım. Bulduğum şey, mümkün ama karmaşık.

İşte buldum:

C ve Go arasındaki geri aramalar hakkında blog yazısı

Cgo belgeleri

Golang posta listesi gönderisi

Herkes bu konuda deneyim var mı? Kısacası, tamamen Go ile yazılmış bir PAM modülü oluşturmaya çalışıyorum.


11
En azından Go'dan oluşturulmamış evrelerden yapamazsınız. Bunu defalarca öfkelendirdim ve giderilene kadar Go'da gelişmeyi bıraktım.
Matt Joiner

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

Go, farklı bir çağrı kuralı ve parçalı yığınlar kullanır. Gccgo ile derlenen Go kodunu C kodu ile bağlayabilirsiniz, ancak gccgo'yu sistemime inşa etmediğim için bunu denemedim.
mkb

Şimdi SWIG kullanarak deniyorum ve umarım ... Henüz çalışacak bir şey almadım ... = '(Posta listesine gönderdim. Umarım birisi bana merhamet eder.
beatgammit

2
Sen olabilir diyoruz C Git kodu, ama şu anda size edemez gömmek önemli ama ince, fark bir C uygulaması içine Git çalışma zamanını.
tylerl

Yanıtlar:


126

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 //exportGit tarafındaki ve externC 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(...)

Evet, ayrı iplikler devreye girdiğinde tüm bunların yüzümüzde patlayabileceğinin farkındayım. Özellikle Go tarafından oluşturulmamış olanlar. Maalesef işler bu noktada.
jimt

17
Bu gerçekten iyi bir cevap ve kapsamlı. Soruyu doğrudan cevaplamıyor, ancak bunun nedeni cevap yok. Birçok kaynağa göre, giriş noktası Go olmalı ve C olamaz. Bunu doğru olarak işaretliyorum çünkü bu gerçekten benim için temizledi. Teşekkürler!
beatgammit

@jimt Bu, çöp toplayıcıyla nasıl bütünleşir? Özellikle, özel progressRequest örneği ne zaman toplanır? (Gitmek ve böylece güvensiz olmak için yeni. Ayrıca, geçersiz bir * kullanıcı verisi alan SQLite3 gibi API'nin yanı sıra kullanıcı verileri için isteğe bağlı bir "silme" işlevine ne dersiniz? Bu, GC ile etkileşim kurmak için kullanılabilir mi, "Git tarafı artık referans vermiyorsa, artık bu kullanıcı verilerini geri istemek sorun değil mi?"
ddevienne

6
Go 1.5'ten itibaren Go'dan C'yi aramak için daha iyi destek var. Daha basit bir teknik gösteren bir cevap arıyorsanız şu soruya bakın: stackoverflow.com/questions/32215509/…
Gabriel Southern

2
Go 1.6'dan itibaren bu yaklaşım işe yaramaz, "C kodu, çağrı döndükten sonra bir Go işaretçisinin bir kopyasını tutamayabilir." kuralı ve "panik: çalışma zamanı hatası: cgo argümanı Git işaretçisine git işaretçisine git" hatası verir
kaspersky

56

Gccgo kullanıyorsanız bu kafa karıştırıcı bir öneri değildir. Burada çalışır:

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

Ben kod var sth ile sth yapmak go package main func Add(a, b string) int { return a + b }hata "undefined _go_string_plus" hata alıyorum
TruongSinh

2
TruongSinh, muhtemelen kullanmak istediğiniz cgove goyerine gccgo. Bkz. Golang.org/cmd/cgo . Bu söylendiğinde, .go dosyasındaki "string" türünü kullanmak ve .c dosyanızı __go_string_plusişlev eklemek üzere değiştirmek tamamen mümkündür . Bu çalışıyor: ix.io/dZB
Alexander


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.