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.
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
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)
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++ |
+---------+----------------+
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.
Lors de l'écriture du processus de conversion des ressources Go en ressources C Il est facile d'écrire avec ptrproxy.
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.
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.
Peut être écrit naturellement comme cgo.
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.
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.
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.
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.
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?
Le fardeau de changer le côté C est faible.
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.
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.
C'est une méthode efficace si le langage RPC est facile à écrire.
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?
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)
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