【问题标题】:cgo: Go pointers in Go memorycgo:Go 内存中的 Go 指针
【发布时间】:2016-08-02 21:43:45
【问题描述】:

代码:

unsafe.Pointer(&du)

其中du 是一些interface 满足以下列表中的规则1?

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

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

换句话说,指向 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-callback 并将 &du 作为参数传递给回调来实现这一点:

//export go_callback
func go_callback(object_ptr unsafe.Pointer) {

    object := *(*Receiver)(object_ptr);
    object.Signal()
}

还有其他方法可以达到吗?

【问题讨论】:

  • 是的,指向接口的指针被认为是“指向包含 Go 指针的 Go 内存的指针”,但是将指向 Go 接口的指针传递给 C 有什么有效用途?
  • @JimB 查看我帖子的更新。我描述了我为什么需要它。
  • unsafe.Pointer(&du)) // this is my suspect 中的&du 应该是&de
  • du 甚至没有被声明,所以这首先无法编译。但最终,一个接口包含 2 个指针值,因此 cgo 不会允许您将指针传递给接口,即使有办法让它工作。
  • @JimB "du" == "de"。谢谢。

标签: go cgo


【解决方案1】:

回答

在@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 &amp;&amp; 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()
}

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

【讨论】:

  • 仅供参考,如果您想知道我的用例出现在哪里,它是一个用 C 编写的高级 kafka 库的包装器,它包装了 librdkafka 并严重依赖回调和传递上下文。
【解决方案2】:

这是解决此问题的另一个技巧。除非您的 C 库不访问所传递的任何内容(例如 C 库仅将其传递给回调),否则不鼓励这样做。

想法是将你的 unsafe.Pointer 转换为 C.ulonglong

调用 C

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

把它扔回去

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

【讨论】:

    猜你喜欢
    • 2021-04-10
    • 2020-09-23
    • 2016-07-12
    • 1970-01-01
    • 2012-04-29
    • 2015-12-18
    • 2016-03-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多