如何 return 错误字符串从转到 python

How to return an error string from go to python

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

panic: runtime error: cgo result has Go pointer

这很奇怪,因为这是一个字符串而不是一个指针。我知道 returning go strings via shared object exported function 没有问题,因为我在其他几个地方这样做没有任何问题。

Go 代码如下所示:

package main

import "C"

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

func main() {}

go 代码使用 buildmode=c-shared 编译为 .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 代码 return 没有错误时,一切都很好并且可以工作!但是,如果 golang 代码试图 return 一个错误(例如 CallSomethingInGo 失败并且 returns 错误)那么 python 代码失败并显示:

panic: runtime error: cgo result has Go pointer

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

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

*os.PathError

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

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

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

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

正如我在评论中所说,你不能这样做。

从C代码调用Go代码的规则在the Cgo documentation, with this particular issue described in this section中有概述,这样(虽然我特别加粗了一些部分):

Passing pointers

Go is a garbage collected language, and the garbage collector needs to know the location of every pointer to Go memory. Because of this, there are restrictions on passing pointers between Go and C.

In this section the term Go pointer means a pointer to memory allocated by Go (such as by using the & operator or calling the predefined new function) and the term C pointer means a pointer to memory allocated by C (such as by a call to C.malloc). Whether a pointer is a Go pointer or a C pointer is a dynamic property determined by how the memory was allocated; it has nothing to do with the type of the pointer.

Note that values of some Go types, other than the type's zero value, always include Go pointers. This is true of string, slice, interface, channel, map, and function types. A pointer type may hold a Go pointer or a C pointer. Array and struct types may or may not include Go pointers, depending on the element types. All the discussion below about Go pointers applies not just to pointer types, but also to other types that include Go pointers.

Go code may pass a Go pointer to C provided the Go memory to which it points does not contain any Go pointers. The C code must preserve this property: it must not store any Go pointers in Go memory, even temporarily. When passing a pointer to a field in a struct, the Go memory in question is the memory occupied by the field, not the entire struct. When passing a pointer to an element in an array or slice, the Go memory in question is the entire array or the entire backing array of the slice.

C code may not keep a copy of a Go pointer after the call returns. This includes the _GoString_ type, which, as noted above, includes a Go pointer; _GoString_ values may not be retained by C code.

A Go function called by C code may not return a Go pointer (which implies that it may not return a string, slice, channel, and so forth). A Go function called by C code may take C pointers as arguments, and it may store non-pointer or C pointer data through those pointers, but it may not store a Go pointer in memory pointed to by a C pointer. A Go function called by C code may take a Go pointer as an argument, but it must preserve the property that the Go memory to which it points does not contain any Go pointers.

Go code may not store a Go pointer in C memory. C code may store Go pointers in C memory, subject to the rule above: it must stop storing the Go pointer when the C function returns.

These rules are checked dynamically at runtime. The checking is controlled by the cgocheck setting of the GODEBUG environment variable. The default setting is GODEBUG=cgocheck=1, which implements reasonably cheap dynamic checks. These checks may be disabled entirely using GODEBUG=cgocheck=0. Complete checking of pointer handling, at some cost in run time, is available via GODEBUG=cgocheck=2.

It is possible to defeat this enforcement by using the unsafe package, and of course there is nothing stopping the C code from doing anything it likes. However, programs that break these rules are likely to fail in unexpected and unpredictable ways.

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

a Go function called by C code

因为 Python 的 cdll 处理程序算作 C 代码。你可以returnnil,因为那是zero-value,但是你不可以return去字符串。 empty-string 常量(以及来自其他一些错误类型的其他字符串常量)在运行时没有被捕获是运气问题。1


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

捕获的情况

reasonably cheap dynamic checks

second-to-last 段中提到。