cgo:Go内存中的Go指针 [英] cgo: Go pointers in Go memory

查看:75
本文介绍了cgo:Go内存中的Go指针的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

执行代码:

unsafe.Pointer(&du)

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

where du is some interface satisfy the rule 1 from the following list?

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

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

Go code may pass a Go pointer to C provided that the Go memory to which it points does not contain any Go pointers. That rule must be preserved during C execution, in that the program must not store any Go pointers into that memory.

换句话说,是否将C指针转到接口视为包含Go指针的Go内存的指针"?

In other words, is the C-pointer to Go interface considered as a "pointer to Go memory containing a Go pointers"?

更新:

我的问题是以下代码:

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 作为参数传递给回调来实现此目的:

My idea is to make C-code calling the Receiver's "method" Signal(). I achieve this by exporting a Go-callback and passing that &du as an argument to the callback:

//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指针".运行程序时出现恐慌.

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.

如果要在运行时使用类似的方法,可以通过使用 GODEBUG = cgocheck = 0 运行程序来禁用恐慌.

If you want to use something like this at runtime, you can disable the panic by running your program with GODEBUG=cgocheck=0.

实际上,我之前已经在go<中编写了这样的代码.1.6封装了从线程C代码异步调用的面向对象的处理程序代码-因此,我认为用例没有那么疯狂.

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.

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

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.

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

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.

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

Not the best C in the world, but here's a quick simplification of other code I've done this with before

库代码

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

执行代码

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

不理会不清理内存分配...

Disregard not cleaning up memory allocations...

如果将go,c包装程序和Makefile放在顶层目录中,则"C库"会在名为library的文件夹中,然后运行 make&&go build (在OS X上,为Linux调整makefile编译器标志),您应该惊慌于"cgo参数has Go指针指向Go指针";使用go> = 1.6并不会因go<1.6运行二进制文件时.使用go 1.6进行构建并使用 GODEBUG = cgocheck = 0 进行运行,应输出已收到测试[116101115116] .

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].

要使此示例在1.6下运行而无需禁用 cgocheck ,请添加如下所示的注册表类型:

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

请注意,在 CreatePublisher callbackWrapper 中添加了注册表代码,现在不再将指针传递给接口,而是将指针传递给接口的索引.注册表.以相同的方式进行编译,不再出现恐慌!

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!

这篇关于cgo:Go内存中的Go指针的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆