【问题标题】:How to return an error string from go to python如何从 go 到 python 返回错误字符串
【发布时间】:2020-11-29 16:18:36
【问题描述】:

我正在用 Go (c-shared) 编写一个共享对象,它将从 python 加载和运行。一切正常,直到 Go 代码需要返回错误。我正在使用 error.Error() 将错误转换为字符串,但是当尝试将其返回给 python 时,cgo 正在命中:

panic: runtime error: cgo result has Go pointer

这很奇怪,因为这是一个字符串,而不是一个指针。我知道通过共享对象导出函数返回 go 字符串没有问题,因为我在其他几个地方这样做没有任何问题。

Go 代码如下所示:

package main

import "C"

//export MyFunction
func MyFunction() string {
    err := CallSomethingInGo()
    if err != nil {
        return err.Error()
    }
    return ""
}

func main() {}

使用 buildmode=c-shared 将 go 代码编译为 .so,然后在 python 代码中,我有这样的内容:

from ctypes import *

lib = cdll.LoadLibrary("./mygocode.so")

class GoString(Structure):
    _fields_ = [("p", c_char_p),("n", c_longlong)]

theFunction = lib.MyFunction
theFunction.restype = GoString

err = theFunction()

当最后一行执行并且 golang 代码返回 NO 错误时,一切都很好并且可以正常工作!但是,如果 golang 代码尝试返回错误(例如 CallSomethingInGo 失败并返回 err),那么 python 代码将失败并显示:

panic: runtime error: cgo result has Go pointer

我已经尝试从 go 手动返回字符串到 python 并且它工作正常,但是尝试返回 error.Error() (根据我的理解应该是一个字符串)失败。将错误的字符串表示形式返回给python的正确方法是什么?

还有一条信息——来自 golang,我做了一个 printf("%T", err) ,我看到错误的类型是:

*os.PathError

我也做了 printf("%T", err.Error()) 并确认 err.Error() 返回的类型是 'string' 所以我仍然不确定为什么这不起作用。

对我来说甚至更陌生...我尝试修改 go 函数,如下所示进行测试,这段代码工作正常并将“test”作为字符串返回给 python...

//export MyFunction
func MyFunction() string {
    err := CallSomethingInGo()
    if err != nil {
        // test
        x := errors.New("test")
        return x.Error()
    }
    return ""
}

我很困惑!该测试如何工作,而不是 err.Error() ?

【问题讨论】:

  • Python 并不恐慌。 Go 运行时就是这样做的。不允许将 Go 字符串(也不是任何 Go 指针)返回到 C 代码中。仔细阅读 cgo 文档的this section
  • 我可以毫无问题地返回一个 go 字符串 - 请参阅我后面的示例,它们工作正常。我还在已导出的多个其他函数中返回字符串(从不出错)。这似乎不是这里的问题。
  • 您可以将 Go 字符串返回给 Go 代码。您不能将一返回给 C 代码。运行时是否会捕获这个错误完全是另一回事,但你不能这样做

标签: python string go error-handling cgo


【解决方案1】:

正如我在评论中所说,您只是不允许这样做。

the Cgo documentation 中概述了从 C 代码调用 Go 代码的规则,this section 中描述了这个特殊问题,以这种方式(尽管我特别加粗了一些部分):

传递指针

Go 是一种垃圾收集语言,垃圾收集器需要知道每个指向 Go 内存的指针的位置。因此,在 Go 和 C 之间传递指针是有限制的。

在本节中,术语 Go 指针表示指向由 Go 分配的内存的指针(例如通过使用 & 运算符或调用预定义的新函数),术语 C 指针表示指向由 C 分配的内存的指针(例如通过对 C.malloc 的调用)。指针是 Go 指针还是 C 指针是由内存分配方式决定的动态属性;它与指针的类型无关。

请注意,除了类型的零值之外,某些 Go 类型的值始终包含 Go 指针。字符串、切片、接口、通道、映射和函数类型都是如此。指针类型可以保存 Go 指针或 C 指针。数组和结构类型可能包含也可能不包含 Go 指针,具体取决于元素类型。下面所有关于 Go 指针的讨论不仅适用于指针类型,还适用于包含 Go 指针的其他类型。

Go 代码可以将 Go 指针传递给 C,前提是它指向的 Go 内存不包含任何 Go 指针。 C 代码必须保留这个属性:它不能在 Go 内存中存储任何 Go 指针,即使是暂时的。当传递一个指向结构体中字段的指针时,所讨论的 Go 内存是该字段占用的内存,而不是整个结构体。当传递指向数组或切片中元素的指针时,所讨论的 Go 内存是整个数组或切片的整个后备数组。

调用返回后,C 代码可能不会保留 Go 指针的副本。这包括 _GoString_ 类型,如上所述,它包括一个 Go 指针; _GoString_ 值可能不会被 C 代码保留。

由 C 代码调用的 Go 函数可能不会返回 Go 指针(这意味着它可能不会返回字符串、切片、通道等)。由 C 代码调用的 Go 函数可以将 C 指针作为参数,并且可以通过这些指针存储非指针或 C 指针数据,但它可能不会将 Go 指针存储在 C 指针指向的内存中。 C 代码调用的 Go 函数可以将 Go 指针作为参数,但它必须保留其指向的 Go 内存不包含任何 Go 指针的属性。

Go 代码可能不会在 C 内存中存储 Go 指针。 C 代码可以将 Go 指针存储在 C 内存中,但须遵守上述规则:当 C 函数返回时,它必须停止存储 Go 指针。

这些规则在运行时动态检查。检查由 GODEBUG 环境变量的 cgocheck 设置控制。默认设置是 GODEBUG=cgocheck=1,它实现了相当便宜的动态检查。可以使用 GODEBUG=cgocheck=0 完全禁用这些检查。可以通过 GODEBUG=cgocheck=2 对指针处理进行完整检查,但需要在运行时付出一些代价。

可以通过使用 unsafe 包来克服这种强制,当然没有什么可以阻止 C 代码做任何它喜欢做的事情。 但是,违反这些规则的程序可能会以意想不到的方式失败。

这就是您所看到的:您有一个程序违反了多个规则,现在它以意想不到和不可预知的方式失败了。特别是,您的lib.MyFunction

C 代码调用的 Go 函数

因为 Python 的 cdll 处理程序算作 C 代码。你可以返回nil,因为那是零值,但是你不能返回 Go 字符串。空字符串常量(以及来自其他一些错误类型的其他字符串常量)在运行时没有被捕获这一事实是幸运的。1


1这是好运还是好运取决于你的观点。如果它一直失败,也许你会更早地查阅 Cgo 文档。相反,它会意外失败,但在最常见的情况下不会。这里发生的是字符串常量被编译为文本(或rodata)部分,因此实际上并不是动态分配的。然而,一些——不是全部,而是一些——错误的字符串字节是动态分配的。一些os.PathErrors 指向支持 GC 的内存,这些是被 GC 捕获的情况

相当便宜的动态检查

在倒数第二段中提到。

【讨论】:

  • 感谢您的澄清和耐心 torek - 我不清楚这是不正确的,因为它一直为我工作(除了它不是!)而且我已经看到了几个例子在返回 GoString 的 github 上。我已将所有字符串/GoString 切换到 *C.char (go) 和 c_char_p (python),现在一切似乎都正常了。
猜你喜欢
  • 1970-01-01
  • 2020-12-12
  • 1970-01-01
  • 2019-02-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-11
  • 2017-08-06
相关资源
最近更新 更多