Boundary between C and Golang

Since few people write C language Advent Calendar 2016, I thought I would write it, remembering when I was touching C language.

from here

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

C-shared example

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)

How to avoid cgocheck

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++          |
+---------+----------------+

Workaround (1)

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.

merit

When writing a process to convert a Go resource to a C resource It is easy to write using ptrproxy.

Demerit

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.

Workaround (2)

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.

merit

Can be written naturally as cgo.

Demerit

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.

Workaround (3)

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.

merit

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.

Demerit

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.

Workaround (4)

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?

merit

The burden of changing the C side is small.

Demerit

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.

Workaround (5)

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.

merit

This is an effective method if the language is easy to write RPC.

Demerit

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?

How to use it properly?

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)

Summary

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

Boundary between C and Golang
Exchange encrypted data between Python and C #
Between parametric and nonparametric
Golang error and panic
Difference between process and job
Correspondence between pandas and SQL
Conversion between unixtime and datetime
using golang slack editing C2
Difference between "categorical_crossentropy" and "sparse_categorical_crossentropy"
Difference between regression and classification
Connection between flask and sqlite3
Difference between np.array and np.arange
Difference between MicroPython and CPython
Cooperation between py2exe and setuptools
Difference between ps a and ps -a
Difference between return and print-Python
Difference between Ruby and Python split
Differences between Windows and Linux directories
Django's MVT-Relationship between Models and Modules-
Difference between java and python (memo)
Difference between list () and [] in Python
Differences between yum commands and APT commands
Difference between SQLAlchemy filter () and filter_by ()
[Note] Conflict between matplotlib and zstd
Function pointer and objdump ~ C language ~
Differences between symbolic links and hard links
Memorandum (difference between csv.reader and csv.dictreader)
Correspondence between RecyclerView and Marker (Kotlin)
(Note) Difference between gateway and default gateway
Difference between Numpy randint and Random randint
Differences between Python, stftime and strptime
Difference between sort and sorted (memorial)
Difference between python2 series and python3 series dict.keys ()
Speed comparison between CPython and PyPy
Python --Difference between exec and eval
[Python] Difference between randrange () and randint ()
[Python] Difference between sorted and sorted (Colaboratory)