1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
|
cshared
=======
Building
--------
go 1.6+
```
go build -o libgogit.so -buildmode=c-shared github.com/src-d/go-git/cshared
```
Two files must appear: libgogit.h and libgogit.so. The second must be
a shared library, not an ar archive (may happen when something goes wrong).
Check the exported symbols with `nm -g`.
How it works
------------
Nearly every public Go function is mirrored in the corresponding *_cshared.go
file. struct fields are also mirrored with getters and setters. The functions
are marked with `//export ...` "magic" cgo comment so that they appear
in defined symbols of a shared library built with `-buildmode=c-shared`.
Go pointers may not be passed out of cgo functions, so we maintain the
two-way registry of all active Go objects mapped to `Handle`-s (`uint64`).
Every time we need to return a reference to Go object outside, we call
`RegisterObject(interface{})` which returns a new `Handle` or reuses
an existing one if the object has already been registered. Then we
return the obtained `Handle`. When we need to receive a Go object reference
in cgo function parameters, we accept `uint64` and retrieve the `interface{}`
with `GetObject(Handle)` which can be casted to the underlying type with a
type assertion. When the object is no longer needed, we invoke
`UnregisterObject(Handle)`.
Although `interface{]` is just two `uintptr`-s inside, it is not a hashable
type and we cannot use it a as key in our backward registry mapping.
We are using the data `uintptr` as the key there. Since several distinct
objects may exist with the same data pointer (e.g. struct and first field
of the struct), the value of that mapping is a slice of `Handle`-s.
All the mentioned service functions are goroutine- and threadsafe.
`std_cshared.go` contains the cgo wrappers for standard library objects.
Debugging
---------
`c_dump_object()` prints the current state of the two-way object registry
to stdout. `c_set_trace()` activates echoing of `RegisterObject()` and
`UnregisterObject()` invocations.
Caveats
-------
Normally, we pass over a pointer to object as `interface{}` into `RegisterObject()`
so that it can be mutated later. It requires the corresponding
pointer-to-type type assertion in cgo functions. If you mess with this,
the cgo function will, of course, panic.
A cgo function is allowed to take Go's `string` parameters. `string`'s
data must point to some memory and cgo does not copy the incoming foreign
memory into Go memory automatically. What's worse, `string`-s are immutable
and when you copy it, the copy points to the same memory. This means that
if you pass in a `string` which was constructed using `malloc()`, for example,
and later `free()` it, all Go strings created from the function parameter
will point to the invalid memory. Actually, this allowance violates the
cgo pointer passing rules stated just several blocks of texts
below the example of string parameters - this is crazy, but we have to live
with this, as usual in Go world. So, *all incoming `string`-s must be immediately
safely copied with `CopyString()` once they are used*.
Returning strings and byte slices is also funny: you have to use `C.CString` -> `*C.char`
and additionally return the length as another result tuple member if needed.
`C.CString` copies the memory pointed by `string` to a `malloc()`-ed region
and it is the responsibility of the other side to `free()` it or it will leak
otherwise.
Another tricky part is in `c_std_map_get_str_str` and similar places
where you need to return `*C.char` from an unaddressed array accessed under
a pseudonym type through reflection. The only way I've found working
is using `reflect.Copy` to byte slice (copy) and then conversion to
`string` (copy), then `C.CString` (copy) and finally another (copy) on the
receiving side because the latter must be `free()`-d. Extremely efficient.
|