使用go modules和cgo时如何解决循环依赖

How to resolve circular dependencies when using go modules and cgo

在我的项目中,我使用回调从 C 到 go 的双向调用,反之亦然。我通过将 C 部分编译到一个库中,然后将 go 部分编译到一个库中,然后最后一个链接器将它们放在一起来解决循环依赖的问题。这在不使用 go 模块时工作正常。 Go 源文件在命令行中明确列出。有人告诉我,从 go 1.12 开始“这不是正确的方法”。

随着项目越来越大,我现在想使用go modules。不幸的是,这改变了 go 编译器的行为。它现在想要解决外部依赖关系并将它们隐式包含在输出文件中。由于循环依赖,它现在总是以未定义的引用或多个定义结束。 “正确”使用 cgo 和 go 模块时如何解决循环依赖?

这是说明问题的最小示例。从 Makefile 中的 go 调用中删除文件名“hello.go”,看看它是如何崩溃的。

这是错误信息:

hello.c:3: multiple definition of `c_hello'; $WORK/b001/_cgo_hello.o:/tmp/go-build/hello.c:3: first defined here

生成文件:

libchello.a: Makefile hello.c
    gcc -fPIC -c -o chello.o hello.c
    ar r libchello.a chello.o
libgohello.a: Makefile hello.go libchello.a
    env CGO_LDFLAGS=libchello.a go build -buildmode=c-archive -o libgohello.a hello.go
main: Makefile main.c libgohello.a libchello.a
    gcc -o main main.c libchello.a libgohello.a -pthread
.PHONY: clean
clean:
    rm -f main *.a *.o
    echo "extern void go_hello();" > libgohello.h

hello.go:

package main
/*
extern void c_hello();
*/
import "C"
import "time"
import "fmt"
//export go_hello
func go_hello() {
    fmt.Printf("Hello from go\n")
    time.Sleep(1 * time.Second)
    C.c_hello()
}
func main() {}

libgohello.h:

extern void go_hello();

hello.c:

#include "libgohello.h"
#include <stdio.h>
void c_hello() {
    printf("Hello from c\n");
    go_hello();
}

main.c:

void c_hello();
int main() {
    c_hello();
}

go.mod:

module hehoe.de/cgocircular

如果您查看 go build 命令的详细输出,您会看到在将目录编译为完整的 go 包时,main.c 文件作为 C 文件的一部分包含在内hello.go.

中使用的代码

来自documentation

When the Go tool sees that one or more Go files use the special import "C", it will look for other non-Go files in the directory and compile them as part of the Go package

这里最简单的解决方案是将主要的 C 和 Go 包分开,这样它们就不会干扰彼此的构建过程。对此进行测试,删除 main.c 文件将构建 libchello.alibgohello.a,然后将其重新添加将完成 main.

的构建