如何在Go和C函数之间传递Go实例?

问题描述 投票:0回答:1

我想将 Go 实例作为参数传递给来自 C 的每个调用,如下面的代码。

我只想将指向 http.Client 实例的指针作为我的 C 代码中的不透明指针进行操作。

我有几个问题。

  • 如何将&http.Client转换为C类型?
  • 如何防止go分配的内存被释放?
  • 如何将C.HTTP_CLIENT_HDL类型转换为&http.Client类型?
  • 如何释放go分配的内存?
typedef size_t HTTP_CLIENT_HDL

HTTP_CLIENT_HDL client_hdl = http_new_client();
int res = http_post(client_hdl, ...);
http_free_client(client_hdl);
/*
typedef size_t HTTP_CLIENT_HDL; // correct type?
*/
import "C"
import (
    "net/http"
)

//export http_new_client
func http_new_client() (client_hdl C.HTTP_CLIENT_HDL){
    client := &http.Client{
        ...
    }
    
    return C.HTTP_CLIENT_HDL(client)    // Is correct?
                                        // How to prevent memory allocated to the client variable from being freed?
}

//export http_post
func http_post(client_hdl C.HTTP_CLIENT_HDL, ...) (ret_val C.int) {
    client := &http.Client???(client_hdl)   // how to convert C.HTTP_CLIENT_HDL to &http.Client?
    
    ...
    req, err := http.NewRequest("POST", ...)
    if err != nil {
        return 0
    }
    ...
    resp, err := client.Do(req)
    ...
    
    return 1
}

//export http_free_client
func http_free_client(client_hdl C.HTTP_CLIENT_HDL){
    client := &http.Client???(client_hdl)   // how to convert C.HTTP_CLIENT_HDL type to &http.Client type?
    
    free???(client)     // how to free the memory for client ?
}
go cgo
1个回答
0
投票

好吧,Volker 是正确的:

cgo
规则规定,如果该内存包含 Go 指针,则不能将指向 Go 分配的内存的 C 端指针(引用)传递给该内存(请参阅 this)。嗯,完整的规则集更复杂,但你最好检查一下手册。

其背后的基本原理是,尽管没有已知的当代 Go 实现采用了moving垃圾收集器,但语言规范并不排除这种实现,这意味着为程序提供支持的 Go 运行时必须能够知道指向特定的所有指针分配内存块,并且如果愿意的话能够更新所有内存块。

因此,正确的实现是维护 Go 分配的

*http.Client
到某些非指针“句柄”的映射,并使 C 代码与它们一起工作 - 如下所示:

package main

import (
    "fmt"
    "net/http"
    "os"
    "sync"
)

/*
#if __STDC_VERSION__ >= 199901L
    #include <stdint.h>
    typedef uint64_t HTTP_CLIENT_HDL;
#else
    typedef unsigned long int HTTP_CLIENT_HDL;
#endif

#include <stdio.h>

HTTP_CLIENT_HDL http_new_client(void);
void http_free_client(HTTP_CLIENT_HDL);
char* http_post(HTTP_CLIENT_HDL, char*);

static int do_something(void) {
    HTTP_CLIENT_HDL h;
    char *err;
    int rc;

    h = http_new_client();

    err = http_post(h, "http://localhost:8080/");
    if (err != NULL) {
        fprintf(stderr, "%s\n", err);
        free(err);
        rc = 0;
        goto cleanup;
    }

    rc = 1;
    printf("OK\n");

cleanup:
    http_free_client(h);

    return rc;
}
*/
import "C"

type httpClients struct {
    mu      sync.Mutex
    serial  C.HTTP_CLIENT_HDL
    clients map[C.HTTP_CLIENT_HDL]*http.Client
}

func (htc *httpClients) init() {
    htc.clients = make(map[C.HTTP_CLIENT_HDL]*http.Client)
}

func (htc *httpClients) new() C.HTTP_CLIENT_HDL {
    htc.mu.Lock()
    defer htc.mu.Unlock()

    c := &http.Client{}

    id := htc.serial
    htc.clients[id] = c
    htc.serial++

    return id
}

func (htc *httpClients) free(id C.HTTP_CLIENT_HDL) {
    htc.mu.Lock()
    defer htc.mu.Unlock()

    delete(htc.clients, id)
}

func (htc *httpClients) get(id C.HTTP_CLIENT_HDL) *http.Client {
    htc.mu.Lock()
    defer htc.mu.Unlock()

    c, ok := htc.clients[id]
    if !ok {
        panic(fmt.Sprintf("no http client with id %d", id))
    }

    return c
}

var htClients httpClients

func init() {
    htClients.init()
}

//export http_new_client
func http_new_client() C.HTTP_CLIENT_HDL {
    return htClients.new()
}

//export http_free_client
func http_free_client(h C.HTTP_CLIENT_HDL) {
    htClients.free(h)
}

//export http_post
func http_post(h C.HTTP_CLIENT_HDL, url *C.char) *C.char {
    c := htClients.get(h)

    req, err := http.NewRequest("POST", C.GoString(url), nil)
    if err != nil {
        return C.CString(err.Error())
    }

    _, err = c.Do(req)
    if err != nil {
        return C.CString(err.Error())
    }

    return nil
}

func main() {
    rc := C.do_something()
    if rc == 0 {
        os.Exit(1)
    }
}

请注意,此代码并不是特别有效,因为每次调用

http_*
函数都会通过互斥体来保护“句柄”到
*http.Client
s 的映射。


旁注:你真的需要分配

http.Client
吗?
net/http
代码应该与默认客户端
http.DefaultClient
一起使用,或者与使用特定参数配置的单个非默认实例一起使用。 IOW,您不需要分配新的
http.Client
来使用
net/http
执行 HTTP 请求,所以也许您一开始就解决了一个非问题。

© www.soinside.com 2019 - 2024. All rights reserved.