dongnao6858 2016-08-02 21:43
浏览 161

cgo:Go内存中的Go指针

Does the code:

unsafe.Pointer(&du)

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

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

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.

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

UPDATE:

My issue is the following code:

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
}

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

Is there another way to reach that?

  • 写回答

2条回答 默认 最新

  • duanjian3338 2016-08-03 15:52
    关注

    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!

    评论

报告相同问题?

悬赏问题

  • ¥100 set_link_state
  • ¥15 虚幻5 UE美术毛发渲染
  • ¥15 CVRP 图论 物流运输优化
  • ¥15 Tableau online 嵌入ppt失败
  • ¥100 支付宝网页转账系统不识别账号
  • ¥15 基于单片机的靶位控制系统
  • ¥15 真我手机蓝牙传输进度消息被关闭了,怎么打开?(关键词-消息通知)
  • ¥15 装 pytorch 的时候出了好多问题,遇到这种情况怎么处理?
  • ¥20 IOS游览器某宝手机网页版自动立即购买JavaScript脚本
  • ¥15 手机接入宽带网线,如何释放宽带全部速度