Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I fill out void* C pointer in Go?

Tags:

I am trying to interface with some C code from Go. Using cgo, this has been relatively straight-forward until I hit this (fairly common) case: needing to pass a pointer to a structure that itself contains a pointer to some data. I cannot seem to figure out how to do this from Go without resorting to putting the creation of the structure into the C code itself, which I'd prefer not to do. Here is a snippet that illustrates the problem:

package main

// typedef struct {
//     int   size;
//     void *data;
// } info;
//
// void test(info *infoPtr) {
//     // Do something here...
// }
import "C"

import "unsafe"

func main() {
    var data uint8 = 5
    info := &C.info{size: C.int(unsafe.Sizeof(data)), data: unsafe.Pointer(&data)}
    C.test(info)
}

While this compiles fine, trying to run it results in:

panic: runtime error: cgo argument has Go pointer to Go pointer

In my case, the data being passed to the C call doesn't persist past the call (i.e. the C code in question digs into the structure, copies what it needs, then returns).

like image 616
Richard Wilkes Avatar asked Jun 25 '16 18:06

Richard Wilkes


People also ask

How do you assign data to a void pointer?

After declaration, we store the address of variable 'data' in a void pointer variable, i.e., ptr. Now, we want to assign the void pointer to integer pointer, in order to do this, we need to apply the cast operator, i.e., (int *) to the void pointer variable.

What does void *) mean in C?

A void pointer is a pointer that has no associated data type with it. A void pointer can hold address of any type and can be typecasted to any type.

How do I cast a void pointer to integer?

If you can you should use a union instead: union { void *ptr; int i; }; Then you can be sure there's space to fit either type of data and you don't need a cast. (Just don't try to dereference the pointer while its got non-pointer data in it.)

Can void pointer be dereferenced?

A void pointer cannot be dereferenced. We get a compilation error if we try to dereference a void pointer. This is because a void pointer has no data type associated with it. There is no way the compiler can know what type of data is pointed to by the void pointer.


1 Answers

See "Passing pointers" section in cgo docs:

Go code may pass a Go pointer to C provided the Go memory to which it points does not contain any Go pointers.

And also:

These rules are checked dynamically at runtime. The checking is controlled by the cgocheck setting of the GODEBUG environment variable. The default setting is GODEBUG=cgocheck=1, which implements reasonably cheap dynamic checks. These checks may be disabled entirely using GODEBUG=cgocheck=0. Complete checking of pointer handling, at some cost in run time, is available via GODEBUG=cgocheck=2.

If you run the snippet you've provided with:

GODEBUG=cgocheck=0 go run snippet.go

Then there is no panic. However, the correct way to go is to use C.malloc (or obtain a "C pointer" from somewhere else):

package main

// #include <stdlib.h>
// typedef struct {
//     int   size;
//     void *data;
// } info;
//
// void test(info *infoPtr) {
//     // Do something here...
// }
import "C"

import "unsafe"

func main() {
    var data uint8 = 5

    cdata := C.malloc(C.size_t(unsafe.Sizeof(data)))
    *(*C.char)(cdata) = C.char(data)
    defer C.free(cdata)

    info := &C.info{size: C.int(unsafe.Sizeof(data)), data: cdata}
    C.test(info)
}

It works because while regular Go pointers are not allowed, C.malloc returns a "C pointer":

Go pointer means a pointer to memory allocated by Go (such as by using the & operator or calling the predefined new function) and the term C pointer means a pointer to memory allocated by C (such as by a call to C.malloc). Whether a pointer is a Go pointer or a C pointer is a dynamic property determined by how the memory was allocated.

Note that you need to include stdlib.h to use C.free.

like image 152
gavv Avatar answered Sep 28 '22 04:09

gavv