Frontière entre C et Golang

Comme peu de gens écrivent le calendrier de l'Avent 2016 en langage C, J'ai pensé que je l'écrirais, me souvenant quand je touchais au langage C.

d'ici

La dernière fois, j'ai présenté Golang, une bibliothèque qui prend en charge plusieurs plates-formes. En fait, à quelle fréquence avez-vous la possibilité d'utiliser des bibliothèques partagées en C?

Go prend en charge une fonction qui fonctionne avec une bibliothèque C / C ++ appelée cgo. Vous pouvez facilement appeler l'API C / C ++ à partir de Go.

Cependant, la fonction pour appeler Go à partir de C / C ++ est assez limitée dans son utilisation. L'utilisation principale est les fonctions implémentées dans Go Je présume qu'il est limité aux cas où vous souhaitez l'utiliser à partir d'une autre langue.

Pour être plus précis, Parce qu'il est gênant de porter les fonctions développées dans Go vers d'autres langues Cela signifie créer une bibliothèque avec c-shared et l'appeler depuis une autre langue. Comme c-shared est une interface C, elle peut être utilisée non seulement en C / C ++ mais aussi. Comme le montre l'exemple ci-dessous, il est possible de créer des liens depuis d'autres langues via l'interface C.

(1) Java -> JNI -> c-shared -> Go (2) JavaScript -> Node(native extension) -> c-shared -> Go (3) ActionScript -> ANE -> c-shared -> Go

Exemple partagé en C

J'ai cherché sur github un projet qui utilise c-shared et j'en ai trouvé un joli.

Mot-clé de recherche site:github.com c-shared buildmode -"golang"

https://github.com/shazow/gohttplib

gohttplib

Dans un projet qui appelle le httpserver de Go depuis C / python Cela semble être l'histoire annoncée au PYCON 2016.

Jetons un coup d'œil au fichier d'en-tête généré par c-shared.

libgohttp.h



typedef struct Request_
{
  const char *Method;
  const char *Host;
  const char *URL;
  const char *Body;
  const char *Headers;
} Request;

typedef unsigned int ResponseWriterPtr;

typedef void FuncPtr(ResponseWriterPtr w, Request *r);

extern void Call_HandleFunc(ResponseWriterPtr w, Request *r, FuncPtr *fn);

...

extern void ListenAndServe(char* p0);

extern void HandleFunc(char* p0, FuncPtr* p1);

extern int ResponseWriter_Write(unsigned int p0, char* p1, int p2);

extern void ResponseWriter_WriteHeader(unsigned int p0, int p1);

Notez que toutes les fonctions partagées en C sont des types primitifs C, Ou il est aligné sur la structure C.

C'est très différent de libgo l'autre jour. Dans libgo, le ptr de Go pourrait être désigné à partir du monde C tel qu'il est, donc J'étais en colère contre cgocheck au moment de l'exécution.

libgo.h


struct request_return {
  GoSlice r0;
  GoInterface r1;
};

extern struct request_return request(GoString p0);

Tant que toutes les fonctions partagées en C sont du côté C, Tout le traitement côté envoi est décrit dans Go (cgo).

gohttplib.go


//export HandleFunc
func HandleFunc(cpattern *C.char, cfn *C.FuncPtr) {
  // C-friendly wrapping for our http.HandleFunc call.
  pattern := C.GoString(cpattern)
  http.HandleFunc(pattern, func(w http.ResponseWriter, req *http.Request) {
...
  // Convert the ResponseWriter interface instance to an opaque C integer
  // that we can safely pass along.
  wPtr := cpointers.Ref(unsafe.Pointer(&w))
  // Call our C function pointer using our C shim.
  C.Call_HandleFunc(C.ResponseWriterPtr(wPtr), &creq, cfn)
  // release the C memory
  C.free(unsafe.Pointer(creq.Method))
  C.free(unsafe.Pointer(creq.Host))
  C.free(unsafe.Pointer(creq.URL))
  C.free(unsafe.Pointer(creq.Body))
  C.free(unsafe.Pointer(creq.Headers))
  ...

responsewriter.go


//export ResponseWriter_WriteHeader
func ResponseWriter_WriteHeader(wPtr C.uint, header C.int) {
  w, ok := cpointers.Deref(wPtr)
  if !ok {
    return
  }
  (*(*http.ResponseWriter)(w)).WriteHeader(int(header))
}

Ce qui est intéressant, c'est que PtrProxy fait une référence indirecte en une seule étape. ref (), deref (), free (), etc. Il est en charge de l'enregistrement, de la référence et de la suppression de (resource id, Go ptr).

ptrproxy.go


type ptrProxy struct {
  sync.Mutex
  count  uint
  lookup map[uint]unsafe.Pointer
}
// Ref registers the given pointer and returns a corresponding id that can be
// used to retrieve it later.
func (p *ptrProxy) Ref(ptr unsafe.Pointer) C.uint
// Deref takes an id and returns the corresponding pointer if it exists.
func (p *ptrProxy) Deref(id C.uint) (unsafe.Pointer, bool)
// Free releases a registered pointer by its id.
func (p *ptrProxy) Free(id C.uint)

Comment éviter cgocheck

Lorsque le pointeur fixé par le côté Go est enseigné au côté C tel quel, Sans savoir que les ressources Go sont déplacées par GC ou référencées par C Il peut être supprimé par GC et il peut s'agir de SEGV à un moment imprévu.

Pour éliminer cette possibilité, cgocheck existe et Puisqu'il est peu probable que le GC se produise immédiatement après la sécurisation des ressources, libgo fonctionne en quelque sorte.

Lorsque vous utilisez Go à partir de c-shared, vous devez éviter ce problème et écrire du code.

Au préalable, le côté Go est appelé du côté C, Dans certains cas, il est possible d'appeler le rappel côté C depuis le côté Go.

De plus, l'histoire est compliquée, donc si vous l'organisez à l'avance, cgo peut être écrit dans les commentaires dans le code source Go ou en C / C ++ en tant que code source séparé, Finalement, il sera construit comme une bibliothèque Go.

python


c-shared   main or wrapper
+---------+----------------+
| Go, cgo | C/C++          |
+---------+----------------+

Solution de contournement (1)

Il n'est pas bon de dire au pointeur du côté C que la ressource est sécurisée du côté Go telle quelle, donc Les méthodes comme gohttplib sont valides. Dans gohttplib, en gérant le pointeur sécurisé par la ressource côté Go avec map Cela est évité en indiquant au côté C l'identifiant de la carte. Cette méthode revient à traiter les ressources du côté Go comme des pointeurs opaques.

mérite

Lors de l'écriture du processus de conversion des ressources Go en ressources C Il est facile d'écrire avec ptrproxy.

Démérite

Fuite de ressources si la carte continue de gérer (id, Go ptr) pour toujours Par conséquent, il est nécessaire de libérer les ressources au bon moment. Dans gohttplib, il est supprimé de la carte par un appel gratuit explicite et est ciblé pour GC.

De plus, étant donné que la méthode nécessite une référence de carte, il y a une surcharge avant et après l'appel. C'est la surcharge de la frontière entre Go et C, donc si vous voulez l'éviter, Il est sûr d'avoir une implémentation conviviale au niveau de l'une des couches.

Solution de contournement (2)

Il serait mauvais d'enseigner le pointeur qui a sécurisé les ressources du côté Go vers le côté C tel quel Le pointeur spécifié dans l'argument de la fonction cgo est protégé du GC. Par conséquent, il est efficace de passer la ressource Go au côté C avec l'argument cgo.

mérite

Peut être écrit naturellement comme cgo.

Démérite

Lors du routage des ressources reçues comme arguments côté C de diverses manières, Il est nécessaire d'écrire le processus de copie du côté C en temps opportun.

Solution de contournement (3)

Il n'est pas bon de dire au pointeur du côté C que la ressource est sécurisée du côté Go telle quelle, donc Passez la mémoire allouée du côté C au côté Go comme argument, etc. Cela peut être évité en copiant la valeur du côté Go vers la ressource C.

mérite

Avec une interface qui vous permet de connaître les ressources et les rappels du côté C Si le côté Go écrit simplement dans la ressource et appelle le rappel, Cette méthode est un moyen simple de travailler ensemble.

Démérite

La valeur est copiée du côté Go vers la ressource du côté C. Parce qu'il est nécessaire d'enseigner les ressources du côté C au côté Go Les arguments peuvent être compliqués. Cela dépend de l'implémentation originale du côté C, mais le changement du côté C peut être un fardeau.

Solution de contournement (4)

Il n'est pas bon de dire au pointeur du côté C que la ressource est sécurisée du côté Go telle quelle, donc Appelez la fonction d'allocation de ressources C du côté Go, Cela peut également être évité en écrivant une valeur à cet endroit, puis en retournant (en transférant le propriétaire) au côté C. Est-ce comme appeler C.malloc () du côté aller et transmettre le pointeur du côté C?

mérite

Le fardeau de changer le côté C est faible.

Démérite

La valeur est copiée du côté Go vers la ressource du côté C. Du côté C, il sera nécessaire de libérer les ressources de manière appropriée, donc L'interface peut être asymétrique lorsqu'elle est vue du côté C.

Solution de contournement (5)

Il existe un moyen d'oublier tous les arguments et ressources partagés en c et de travailler avec d'autres RPC. Par exemple, la seule interface exposée à c-shared est start / shutdown, Voici une méthode de conversation avec l'API REST pour un port spécifique.

mérite

C'est une méthode efficace si le langage RPC est facile à écrire.

Démérite

Il y a une surcharge associée à l'utilisation de RPC. Vous devrez peut-être également envisager la gestion des erreurs spécifiques à RPC. N'est-ce pas lié au langage C?

Comment l'utiliser correctement?

Je pense qu'en supposant que le côté Go est appelé du côté C Lorsque PRC est pris en charge à l'avance (5) Fondamentalement (2) Si vous souhaitez exporter une méthode Go compliquée vers le côté C tel quel (1) Si vous voulez passer des octets dans le callback (3) Des méthodes auxiliaires du système create () / free () de structure C compliquée sont disponibles. Si vous voulez le faire du côté Go (4)

Résumé

Le langage C est génial et c'est génial car il peut être réutilisé de différentes manières!

Utilisons Golang, une bibliothèque multi-plateforme!

J'ai essayé de présenter 3 options comme l'utilisation de c-shared, Il y en a un qui est effectivement adopté. laquelle. Le gestionnaire qui recommande une telle méthode de mise en œuvre (ㅍ _ ㅍ)

Recommended Posts

Frontière entre C et Golang
Communication de données chiffrées entre Python et C #
Entre paramétrique et non paramétrique
Erreur de Golang et panique
Différence entre processus et travail
Conversion entre unixtime et datetime
utilisation de golang slack édition C2
Différence entre "categorical_crossentropy" et "sparse_categorical_crossentropy"
Différence entre régression et classification
Différence entre np.array et np.arange
Différence entre MicroPython et CPython
Coopération entre py2exe et les outils de configuration
Différence entre ps a et ps -a
Différence entre return et print-Python
Différence entre Ruby et Python Split
Différence entre les répertoires Windows et Linux
MVT-Relation entre le modèle et le module de Django
Différence entre java et python (mémo)
Différence entre list () et [] en Python
Différence entre la commande yum et la commande APT
Différence entre SQLAlchemy filter () et filter_by ()
Pointeur de fonction et objdump ~ Langage C ~
Mémorandum (différence entre csv.reader et csv.dictreader)
Correspondance entre RecyclerView et Marker (Kotlin)
(Remarque) Différence entre la passerelle et la passerelle par défaut
Différence entre le randint de Numpy et le randint de Random
Différence entre Python, stftime et strptime
Différence entre tri et tri (mémorial)
Différence entre la série python2 et la série python3 dict.keys ()
Comparaison de vitesse entre CPython et PyPy
Python - Différence entre exec et eval
[Python] Différence entre randrange () et randint ()
[Python] Différence entre trié et trié (Colaboratoire)