回答
在@JimB 的后续行动中,是的,这被认为是指向包含 Go 指针的 Go 内存的指针,因此在 go >= 1.6 中,当您执行“cgo 参数具有指向 Go 指针的 Go 指针”时出现恐慌运行你的程序。
如果你想在运行时使用类似的东西,你可以通过使用GODEBUG=cgocheck=0 运行你的程序来禁用恐慌。
实际上我之前在 go
替代方案
将指针直接传递给底层 C 代码的一种可能替代方法是为您的处理程序创建一个线程安全的全局注册表,因此您基本上可以将一些索引传递给注册表到 C 代码中,在您的回调中接收它,看看启动该索引的处理程序,然后在其上调用函数。
示例
这些有点冗长,但给出了一个实际的工作示例。如果你只想看一下注册表实现示例,请跳到底部。
直接指针示例(您的问题)
不是世界上最好的 C,但这里是我之前使用过的其他代码的快速简化
库代码
生成文件:
libtesting:
gcc -fPIC -c library/testing.c -o library/testing.o
gcc -dynamiclib library/testing.o -o library/libtesting.dylib
C 头文件:
/* 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 来源:
/* 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;
}
}
转码
C 包装器:
/* testing_c.c */
#include "_cgo_export.h"
void cgo_callback_wrapper(void * context, char *message, int message_len) {
callbackWrapper(context, message, message_len);
}
去:
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()
}
忽略不清理内存分配...
如果你将 go、c wrappers 和 Makefile 放在顶层目录中,将“C 库”放在名为 library 的文件夹中并运行 make && go build(在 OS X 上,为 Linux 调整 makefile 编译器标志)你使用 go >= 1.6 时应该会出现“cgo 参数具有指向 Go 指针的 Go 指针”的恐慌,并且在运行二进制文件时不会出现 go GODEBUG=cgocheck=0 运行应该输出 Test received [116 101 115 116]。
注册表示例(替代)
要让这个例子在 1.6 下运行而不禁用cgocheck,添加一个类似这样的注册表:
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()
}
注意在CreatePublisher 和callbackWrapper 中添加了注册表代码,现在我们不再传递指向接口的指针,而是传递指向注册表中接口索引的指针。以相同的方式编译,不再出现恐慌!