Answer
In follow up to @JimB, yes, this is considered a pointer to Go memory containing a Go pointer, so in go >= 1.6 you're going to get "cgo argument has Go pointer to Go pointer" panics when you run your program.
If you want to use something like this at runtime, you can disable the panic by running your program with GODEBUG=cgocheck=0
.
I've actually written code like this before in go < 1.6 to wrap object-oriented handler code that's called asynchronously from threaded C code--so I don't think the use-case is that crazy.
Alternatives
One possible alternative to passing the pointer directly to the underlying C code is to make a threadsafe global registry for your handlers, so you would basically pass some index to the registry into the C code, receive it back in your callback, look up the handler for that index and then invoke the function on it.
Examples
These are a bit lengthy, but give an actual working example. If you want to just take a look at the registry implementation example, jump to the bottom.
Direct Pointer Example (Your issue)
Not the best C in the world, but here's a quick simplification of other code I've done this with before
Library Code
Makefile:
libtesting:
gcc -fPIC -c library/testing.c -o library/testing.o
gcc -dynamiclib library/testing.o -o library/libtesting.dylib
C Header:
/* library/testing.h */
#ifndef __TESTING_H__
#define __TESTING_H__
#include <pthread.h>
struct worker_node {
pthread_t worker;
struct worker_node *next;
};
// Structs for publisher
struct publisher {
void (* callback)(void *, char *, int);
void *context;
struct worker_node *workers;
};
struct publisher * publisher_new(void *, void (*)(void *, char *, int));
void publisher_cleanup(struct publisher *);
void publisher_finish(struct publisher *);
void publisher_publish(struct publisher *, char *, int);
#endif // __TESTING_H__
C Source:
/* library/testing.c */
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "testing.h"
struct message_wrapper {
void * context;
char * message;
int message_len;
void (* callback)(void *, char *, int);
};
struct publisher * publisher_new(void *context, void (*callback)(void *, char *, int)) {
struct publisher * self = (struct publisher *)malloc(sizeof(struct publisher));
assert(self);
assert(self->callback = callback);
self->context = context;
self->workers = NULL;
return self;
}
void publisher_cleanup(struct publisher * self) {
struct worker_node * next_node;
struct worker_node * node = self->workers;
while (node != NULL) {
next_node = node->next;
free(node);
node = next_node;
}
free(self);
self = NULL;
}
static void * publisher_worker_thread(void * args) {
struct message_wrapper * wrapper = (struct message_wrapper *)args;
wrapper->callback(wrapper->context, wrapper->message, wrapper->message_len);
free(wrapper->message);
free(wrapper);
pthread_exit(NULL);
}
void publisher_publish(struct publisher *self, char * message, int message_len) {
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
struct worker_node * new_node = (struct worker_node *)malloc(sizeof(struct worker_node));
new_node->next = self->workers;
self->workers = new_node;
struct message_wrapper *wrapper = (struct message_wrapper *)malloc(sizeof(struct message_wrapper));
wrapper->message = malloc(message_len);
memcpy(wrapper->message, message, message_len);
wrapper->message_len = message_len;
wrapper->context = self->context;
wrapper->callback = self->callback;
assert(!pthread_create(&self->workers->worker, &attr, publisher_worker_thread, (void *)wrapper));
}
void publisher_finish(struct publisher *self) {
struct worker_node * node = self->workers;
while (node != NULL) {
assert(!pthread_join(node->worker, NULL));
node = node->next;
}
}
Go Code
C wrapper:
/* testing_c.c */
#include "_cgo_export.h"
void cgo_callback_wrapper(void * context, char *message, int message_len) {
callbackWrapper(context, message, message_len);
}
Go:
package main
/*
#cgo LDFLAGS: -lpthread -Llibrary -ltesting
#include "library/testing.h"
extern void cgo_callback_wrapper(void * context, char *message, int message_len);
*/
import "C"
import (
"fmt"
"unsafe"
)
type Handler interface {
HandleMessage([]byte)
}
type Publisher struct {
base *C.struct_publisher
}
//export callbackWrapper
func callbackWrapper(cContext unsafe.Pointer, cMessage *C.char, cMessageSize C.int) {
handler := *(*Handler)(cContext)
message := C.GoBytes(unsafe.Pointer(cMessage), cMessageSize)
handler.HandleMessage(message)
}
func (p *Publisher) Publish(message []byte) {
cMessage := (*C.char)(unsafe.Pointer(&message[0]))
cMessageLen := C.int(len(message))
C.publisher_publish(p.base, cMessage, cMessageLen)
}
func CreatePublisher(handler Handler) *Publisher {
return &Publisher{
base: C.publisher_new(unsafe.Pointer(&handler), (*[0]byte)(C.cgo_callback_wrapper)),
}
}
func (p *Publisher) Finish() {
C.publisher_finish(p.base)
}
//////// EXAMPLE ////////
type TestHandler struct {
name string
}
func (h TestHandler) HandleMessage(message []byte) {
fmt.Printf("%s received %v", h.name, message)
}
func main() {
handler := TestHandler{name: "Test"}
publisher := CreatePublisher(handler)
publisher.Publish([]byte("test"))
publisher.Finish()
}
Disregard not cleaning up memory allocations...
If you put the go, c wrappers, and Makefile in the top-level directory, the "C library" in a folder named library and run make && go build
(on OS X, adjust the makefile compiler flags for Linux) you should get a panic of "cgo argument has Go pointer to Go pointer" using go >= 1.6 and no panic for go < 1.6 when running the binary. Building with go 1.6 and running with GODEBUG=cgocheck=0
should output Test received [116 101 115 116]
.
Registry Example (Alternative)
To make this example run under 1.6 without disabling cgocheck
add a registry kind of like this:
package main
/*
#cgo LDFLAGS: -lpthread -Llibrary -ltesting
#include "library/testing.h"
extern void cgo_callback_wrapper(void * context, char *message, int message_len);
*/
import "C"
import (
"fmt"
"sync"
"unsafe"
)
var registry map[int]Handler
var handlers int
var mutex = sync.Mutex{}
type Handler interface {
HandleMessage([]byte)
}
type Publisher struct {
base *C.struct_publisher
}
//export callbackWrapper
func callbackWrapper(cContext unsafe.Pointer, cMessage *C.char, cMessageSize C.int) {
mutex.Lock()
handler := registry[*(*int)(cContext)]
mutex.Unlock()
message := C.GoBytes(unsafe.Pointer(cMessage), cMessageSize)
handler.HandleMessage(message)
}
func (p *Publisher) Publish(message []byte) {
cMessage := (*C.char)(unsafe.Pointer(&message[0]))
cMessageLen := C.int(len(message))
C.publisher_publish(p.base, cMessage, cMessageLen)
}
func CreatePublisher(handler Handler) *Publisher {
mutex.Lock()
index := handlers
handlers++
if registry == nil {
registry = make(map[int]Handler)
}
registry[index] = handler
mutex.Unlock()
return &Publisher{
base: C.publisher_new(unsafe.Pointer(&index), (*[0]byte)(C.cgo_callback_wrapper)),
}
}
func (p *Publisher) Finish() {
C.publisher_finish(p.base)
}
//////// EXAMPLE ////////
type TestHandler struct {
name string
}
func (h TestHandler) HandleMessage(message []byte) {
fmt.Printf("%s received %v", h.name, message)
}
func main() {
handler := TestHandler{name: "Test"}
publisher := CreatePublisher(handler)
publisher.Publish([]byte("test"))
publisher.Finish()
}
Notice the addition of registry code in CreatePublisher
and callbackWrapper
and now instead of passing a pointer to an interface we now just pass a pointer to the index of the interface in the registry. Compile the same way and no more panics!