我想将 Go 实例作为参数传递给来自 C 的每个调用,如下面的代码。
我只想将指向 http.Client 实例的指针作为我的 C 代码中的不透明指针进行操作。
我有几个问题。
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 ?
}
好吧,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 请求,所以也许您一开始就解决了一个非问题。