How to return a Go struct to C

1,288 views
Skip to first unread message

Song Liu

unread,
Mar 20, 2017, 9:25:01 AM3/20/17
to golang-nuts
Hi,

I have a shared lib as bellow:

/*
#include <stdint.h>

struct Media {
    char*    Name;
    char*    Path;
    uint64_t Size;
};
*/
import "C"

//export GetMediaFromDB

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

Could you help with the correct way to return a struct from Go ?

Many thanks.

Thanks,
Song

Steven Hartland

unread,
Mar 20, 2017, 9:43:38 AM3/20/17
to golan...@googlegroups.com
--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Song Liu

unread,
Mar 21, 2017, 2:25:41 AM3/21/17
to golang-nuts
Thanks for your info, it seems that it's reporting the same error but still not have a solution.

After tried to use unsafe:
return (*C.struct_Media)(unsafe.Pointer(c_struct_media))

it reports same error:
panic: runtime error: cgo result has Go pointer

Any further insights about this issue? many thanks.

Konstantin Khomoutov

unread,
Mar 21, 2017, 4:33:29 AM3/21/17
to Song Liu, golang-nuts
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.

Konstantin Khomoutov

unread,
Mar 21, 2017, 5:05:12 AM3/21/17
to Song Liu, Konstantin Khomoutov, golang-nuts
On Tue, 21 Mar 2017 11:23:39 +0300
Konstantin Khomoutov <flat...@users.sourceforge.net> wrote:

[...]

Sorry, a couple of assorted fixes:

> A sketch would look something like this:
>
> /*
> typedef MediaHandle uint64_t

Should be

typedef uint64_t MediaHandle

of course.

> */
> 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
> }

This one is better formulated to return a value of proper type
representing the handle:

func GetMediaFromDB(filename *C.char) C.MediaHandle {
c_struct_media := &C.struct_Media{}

handle := C.MediaHandle(uintptr(unsafe.Pointer(c_struct_media)))
Reply all
Reply to author
Forward
0 new messages