如何在使用Go模块和CGO时解决循环依赖问题

huangapple go评论122阅读模式
英文:

How to resolve circular dependencies when using go modules and cgo

问题

在我的项目中,我正在使用回调函数来实现从C到Go的双向调用,使用CGO。我通过将C部分编译为库,然后将Go部分编译为库,最后进行链接,解决了循环依赖的问题。当不使用Go模块时,这个方法运行良好。Go源文件在命令行中明确列出。有人告诉我,从Go 1.12开始,“这不是正确的方法”。

随着项目的发展,我现在想使用Go模块。不幸的是,这改变了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

Makefile:

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
英文:

In my project, I am using callbacks for bi-directional calls from C into go and vice versa using CGO. I resolved the issue of circular dependencies by compiling the C part into a library, then compiling the go part into a library, then a final linker pass puts it all together. This is working fine when not using go modules. Go source files are listed on the command line explicitly. I have been told that as of go 1.12 "this is not the right way to do it".

As the project has grown, I now want to use go modules. Unfortunately, this changes the behaviour of the go compiler. It now wants to resolve external dependencies and implicitly includes them in the output file. Due to the circular dependency, it now always ends up with an undefined reference or multiple definitions. How to resolve circular dependencies when using cgo and go modules "the right way"?

This is a minimal example to illustrate the problem. Remove the file-name "hello.go" from the call to go in the Makefile to see how it falls apart.

This is the error message:

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

Makefile:

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 &quot;extern void go_hello();&quot; &gt; libgohello.h

hello.go:

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

libgohello.h:

extern void go_hello();

hello.c:

#include &quot;libgohello.h&quot;
#include &lt;stdio.h&gt;
void c_hello() {
    printf(&quot;Hello from c\n&quot;);
    go_hello();
}

main.c:

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

go.mod:

module hehoe.de/cgocircular

答案1

得分: 3

如果你查看go build命令的详细输出,你会发现在将目录编译为完整的Go包时,main.c文件被包含在hello.go中使用的C代码的一部分中。

根据文档

> 当Go工具发现一个或多个Go文件使用特殊的导入C时,它会查找目录中的其他非Go文件,并将它们编译为Go包的一部分。

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

英文:

If you look at the verbose output from the go build command, you will see that when compiling the directory as a complete go package, the main.c file is being included as part of the C code used in hello.go.

From the 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

The easiest solution here is to separate the main C and Go packages, so that they don't interfere with each other's build process. Testing this out, removing the main.c file will build libchello.a and libgohello.a, and then adding it back in will complete the build of main.

huangapple
  • 本文由 发表于 2022年1月30日 18:02:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/70913606.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定