On Mon, 20 Mar 2017 06:25:01 -0700 (PDT)
Song Liu <
song...@gmail.com> wrote:
[...]
> struct Media {
>
> char* Name;
>
> char* Path;
>
> uint64_t Size;
>
> };
[...]
> func GetMediaFromDB(filename *C.char) *C.struct_Media {
>
>
> c_struct_media := &C.struct_Media{}
>
>
> return c_struct_media
>
> }
>
>
> Which is called in another program written by C, but when it's
> running it report error:
>
> panic: runtime error: cgo result has Go pointer
The function GetMediaFromDB() allocates an instance of C.struct_Media
on the heap owned by the Go runtime, and returns it.
If you call this function from Go code, all is well.
But if you "mediate" a call to it through C code, that function ends up
returning what it allocated on the Go runtime's heap to C, and this
violates the rules.
You have two approaches to deal with it.
First, as a general rule, whatever Go functions you export to the C
interface, must simply return nothing allocated by Go, period.
You can make them accept pointers to C-allocated values to require the
C side deal with allocation. For example, turn your
func GetMediaFromDB(fname *C.char) *C.struct_Media
into
func InitMediaFromDB(m *C.struct_Media, fname *C.char)
and require the C side to do the necessary dance:
struct Media *mp;
mp = malloc(sizeof(*mp));
if (mp == NULL) {
die("Out of memory\n");
}
InitMediaFromDB(*mp, fname);
/* ... now make sure mp is eventually free()d */
Alternatively you may make your GetMediaFromDB() call C.malloc() itself
to make the value's memory owned by C.
It's ugly:
import "unsafe"
c_struct_media := (*C.struct_Media)(C.malloc(
C.size_t(unsafe.Sizeof(C.struct_Media{}))
))
...
return c_struct_media;
And now you must have somehow make sure this memory will be eventually
freed by a call to C.free(). So you must either create some explicit
"destructor" function to get rid of values produced by GetMediaFromDB()
or define in your API docs that the C code which called GetMediaFromDB()
owns the returned value and must free() it itself.
Second, you can "abstract away" what your Go code returns and make sure
the API exported to C by the Go side only hands out to C opaque values
and requires calling functions on them to access what is stored in them.
You `typedef` all the types you return as something like int64_t,
and make all API definitions in terms of these types.
Now in your Go code you allocate the values, put them into some global
data structure, have a unique key for each -- a "handle", and pass it to
C instead of the actual value. In API functions which are called from C
to access these values via handls first fetch actual value given the
handle and then operate on it.
A sketch would look something like this:
/*
typedef MediaHandle uint64_t
*/
import "C"
import "unsafe"
var media = map[uintptr]C.struct_Media
//export GetMediaFromDB
func GetMediaFromDB(filename *C.char) unitptr {
c_struct_media := &C.struct_Media{}
handle := uintptr(unsafe.Pointer(c_struct_media))
media[handle] = c_struct_media
return handle
}
//export GetPath
func GetPath(handle C.MediaHandle) *C.char {
c_struct_media := media[handle]
return c_struct_media.Path
}
This is slow but 100% memory safe.
Note two things:
* We're using an address of the allocated value as the handle.
But since we produce a non-pointer value out of it via the
idiomatic uintptr(unsafe.Pointer(addr)) type conversion, it's
OK to pass it to C.
* The allocated values end up being referenced by a globally-allocated
map value, and are hence not garbage collected unless explicitly
deleted from there. As you can see, you again would need certain
explicit means to "delete" the values you allocate.
In the end note that it's OK to pass a Go pointer to C but only during
the lifetime of that call to C, and provided the C side does not itself
store that pointer to access it later. So your problem is caused by
the fact what GetMediaFromDB() returns is "passed through" C and ends
up being passed from there back to Go, which is against the rules.
If you would merely call GetMediaFromDB() from C, and not Go->C->Go,
that would be OK.