使用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
.
中使用的代码
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.a
和 libgohello.a
,然后将其重新添加将完成 main
.
的构建
在我的项目中,我使用回调从 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
.
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.a
和 libgohello.a
,然后将其重新添加将完成 main
.