Since few people write C language Advent Calendar 2016, I thought I would write it, remembering when I was touching C language.
Last time, I introduced Golang, a library that supports multiple platforms. In fact, how often do you have the opportunity to use c-shared libraries?
Go supports a function that works with a C / C ++ library called cgo. You can easily call the C / C ++ API from Go.
However, the function to call Go from C / C ++ is quite limited in its usage. The main usage is the functions implemented in Go I presume that it is limited to cases where you want to use it from another language.
To be more specific, Because it is troublesome to port the functions developed in Go to other languages, It means to make a library with c-shared and call it from another language. Since c-shared is a C interface, it can be used not only in C / C ++. As shown in the example below, it is possible to link from other languages via the C interface.
(1) Java -> JNI -> c-shared -> Go (2) JavaScript -> Node(native extension) -> c-shared -> Go (3) ActionScript -> ANE -> c-shared -> Go
When I searched for a project using c-shared from github, I found a nice one.
Search keyword site:github.com c-shared buildmode -"golang"
https://github.com/shazow/gohttplib
gohttplib
In a project that calls Go's httpserver from C / python It seems to be the material announced at PYCON 2016.
Let's take a look at the header file generated by 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);
It should be noted that all c-shared functions are C primitive types, Or it is aligned with the C struct.
It's very different from libgo the other day. In libgo, Go's ptr can be referenced from the C world as it is, so I was angry at cgocheck at runtime.
libgo.h
struct request_return {
GoSlice r0;
GoInterface r1;
};
extern struct request_return request(GoString p0);
As long as all c-shared functions are on the C side, All the processing on the sending side is described in 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))
}
What's interesting is that PtrProxy makes a one-step indirect reference. ref (), deref (), free (), etc. It is in charge of registration, reference, and deletion of (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)
When the pointer secured by the Go side is taught to the C side as it is, Without knowing that Go resources are being moved by GC or referenced by C It may be deleted by GC, and it may be SEGV at an unintended timing.
To eliminate that possibility, cgocheck exists and Since GC is unlikely to occur immediately after securing resources, libgo works somehow.
When using Go from c-shared, you need to avoid that problem and write code.
As a prerequisite, the Go side is called from the C side, In some cases, it is possible to call the C side callback from the Go side.
Also, the story is complicated, so if you organize it in advance, cgo can be written in comments in the Go source code or in C / C ++ as a separate source code, Eventually it will be built as a Go library.
python
c-shared main or wrapper
+---------+----------------+
| Go, cgo | C/C++ |
+---------+----------------+
It is not good to teach the pointer that the resource is secured on the Go side to the C side as it is, so Methods like gohttplib are valid. In gohttplib, while managing the pointer secured by the resource on the Go side with map It is avoided by telling the C side the id of the map. This method is like treating the resource on the Go side as an opaque pointer.
When writing a process to convert a Go resource to a C resource It is easy to write using ptrproxy.
Resource leak if map keeps handling (id, Go ptr) forever Therefore, it is necessary to release resources at the right time. In gohttplib, it is deleted from the map by an explicit free call and is targeted for GC.
Also, since the method requires a map reference, there is some overhead before and after the call. It's the overhead of the boundary between Go and C, so if you want to avoid it, It's safe to have a performance-friendly implementation at one of the layers.
It would be bad to teach the pointer that the resource was secured on the Go side to the C side as it is. The pointer specified in the argument of the cgo function is guarded from the GC. Therefore, it is effective to pass the Go resource to the C side with the cgo argument.
Can be written naturally as cgo.
When routing the resources received as arguments on the C side in various ways, It is necessary to write the copy process on the C side in a timely manner.
It is not good to teach the pointer that the resource is secured on the Go side to the C side as it is, so Pass the memory allocated on the C side to the Go side as an argument, etc. This can be avoided by copying the value from the Go side to the C resource.
With an interface that lets you know resources and callbacks from the C side If the Go side just writes to the resource and calls the callback, This method is an easy way to work together.
The value is copied from the Go side to the C side resource. Because it is necessary to teach the resources on the C side to the Go side The arguments may get complicated. It depends on the original C-side implementation, but changing the C-side can be a burden.
It is not good to teach the pointer that the resource is secured on the Go side to the C side as it is, so Call the C resource allocation function from the Go side, It can also be avoided by writing a value there and then returning (transferring the owner) to the C side. Is it like calling C.malloc () on the go side and telling the pointer to the C side?
The burden of changing the C side is small.
The value is copied from the Go side to the C side resource. On the C side, it will be necessary to release resources appropriately, so The interface may be asymmetric when viewed from the C side.
There is a way to forget all the arguments and resources of c-shared and cooperate with other RPCs. For example, the only interface exposed to c-shared is start / shutdown, The following is a method of talking with the REST API for a specific port.
This is an effective method if the language is easy to write RPC.
There is an overhead associated with using RPC. You may also need to consider RPC-specific error handling. Isn't this related to C language?
I think on the premise of calling the Go side from the C side When PRC is supported in advance (5) Basically (2) If you want to export a complicated Go method to the C side as it is (1) If you want to pass bytes in callback (3) Auxiliary methods of create () / free () system of complicated C structure are available. If you want to make it on the Go side (4)
C language is great and it's great because it can be reused in various ways!
Let's use Golang, a multi-platform library!
I tried to present 3 options as the usage of c-shared, There is one that is actually adopted. which one. A manager who recommends such an implementation method (ㅍ _ ㅍ)
Recommended Posts