cgo:Go内存中的Go指针

  • 本文关键字:Go 指针 内存 cgo go cgo
  • 更新时间 :
  • 英文 :


执行代码:

unsafe.Pointer(&du)

其中CCD_ 1是某个CCD_ 2满足以下列表中的规则1吗?

https://github.com/golang/go/issues/12416

Go代码可以将Go指针传递给C,前提是Go内存它所指向的不包含任何Go指针。该规则必须在C执行期间保留,因为程序不能存储任何把指针指向那个记忆。

换句话说,指向Go interface的C指针是否被视为"指向包含Go指针的Go内存的指针"?

更新:

我的问题是以下代码:

type Receiver interface {
    Signal()
}
func WowFunction(data []byte, du Receiver) {
    C.wow_function( (*C.char)( unsafe.Pointer(&data[0]) ), 
                    (C.size_t)(len(data)),
                    unsafe.Pointer(&du))    // this is my suspect
}

我的想法是让C代码调用Receiver的"方法"Signal()。我通过导出Go回调并将&du作为参数传递给回调来实现这一点:

//export go_callback
func go_callback(object_ptr unsafe.Pointer) {
    object := *(*Receiver)(object_ptr);
    object.Signal()
}

还有别的办法吗?

答案

在@JimB的后续中,是的,这被认为是一个指向Go内存的指针,包含Go指针,所以在Go中>=1.6你会得到";cgo参数具有Go指针到Go指针"运行程序时会感到恐慌。

如果您想在运行时使用类似的东西,可以通过使用GODEBUG=cgocheck=0运行程序来禁用死机。

实际上,我以前在go<1.6来包装从线程C代码异步调用的面向对象处理程序代码——所以我不认为这个用例有那么疯狂。

备选方案

将指针直接传递到底层C代码的一种可能的替代方法是为处理程序创建一个线程安全的全局注册表,因此您基本上将一些指向注册表的索引传递到C代码中,在回调中接收它,查找该索引的处理程序,然后调用其上的函数。


示例

这些有点冗长,但给出了一个实际的工作示例。如果您只想看一看注册表实现示例,请跳到底部。

直接指针示例(您的问题)

不是世界上最好的C,但下面是我在之前使用过的其他代码的快速简化

库代码

Makefile:

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;
  }
}

Go代码

C包装:

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

忽略未清理内存分配。。。

如果将go、c包装器和Makefile放在顶级目录中;C库";在一个名为library的文件夹中,运行make && go build(在OSX上,调整Linux的makefile编译器标志),您应该会感到恐慌;cgo参数具有Go指针到Go指针"使用go>=1.6并且对于go没有恐慌<1.6运行二进制文件时。使用go 1.6构建并使用GODEBUG=cgocheck=0运行应该会输出du0。

注册表示例(可选)

要使此示例在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()
}

注意在CreatePublishercallbackWrapper中添加了注册表代码,现在我们不传递指向接口的指针,而是只传递指向注册表中接口索引的指针。以同样的方式编译,不再恐慌!

这里有另一个解决方法。除非你的C库没有访问传递的内容(就像C库只为回调传递它一样),否则不鼓励这样做。

想法是投你的不安全。指向C.ulonglong 的指针

呼叫C

C.function(C.ulonglong(uintptr(unsafe.Pointer(&something))))

投射回

(*SomethingType)(unsafe.Pointer(uintptr(the_c_long_value)))

最新更新