在 Crystal-lang 中编写 Python 函数时出错

Error in writing Python functions in Crystal-lang

我正在尝试通过 C Python API.

在 crystal-lang 中编写一些 python 函数

我的代码如下:

METH_VARARGS  = 0x0001

@[Link("python3.5m")]
lib Python
  alias PyObject = Void*

  struct PyMethodDef
    name  : UInt8*
    func  : Void*
    flags : LibC::Int
    doc   : UInt8*
  end

  fun Py_Initialize
  fun Py_Finalize
  fun PyObject_CallObject(func : PyObject, args : PyObject) : PyObject
  fun PyCFunction_NewEx(method : PyMethodDef*, __self__ : PyObject, ) : PyObject
  fun PyLong_AsLong(n : PyObject) : Int64
  fun PyLong_FromLong(n : Int64) : PyObject

end

def new_method_def(name : String, function, flags : LibC::Int)
  x = Pointer(Python::PyMethodDef).malloc(1)
  x.value.name  = name.to_unsafe
  x.value.func  = function
  x.value.flags = flags
  x.value.doc   = nil
  x
end

Python.Py_Initialize

a = ->(args : Void*) { 
                       puts Python.PyLong_AsLong(args)
                       Pointer(Void).null 
                     }

name     = "num"
number   = Python.PyLong_FromLong(1) 
Python.Py_IncRef(number)
method   = Python.PyCFunction_NewEx(new_method_def(name,a.pointer,METH_VARARGS),number)
Python.PyObject_CallObject(method,Pointer(Void).null)

Python.Py_Finalize

如果我在 PyCFunction_NewEx 中设置 nil 而不是 number,一切正常,但正如代码所示,当 Py_Finalize 为叫。 我不明白是什么原因造成的。 有人能帮我吗?

这里的根本问题是您调用了一个只有两个参数的三个参数的 C 函数。

遗憾的是,PyCFunction_NewEx is missing from the documentation, despite being a public API function. But all of the examples using it pass three arguments. And if you go to the source:

PyObject *
PyCFunction_NewEx(PyMethodDef *ml, PyObject *self, PyObject *module)

那是 3.7,但这是相同的 in 3.5 and in 2.7, and in every other version since the function was added to the API in 2.3NewEx 的全部意义在于允许您传递一个模块。

据推测,该函数期望第三个参数在寄存器或堆栈中,而您没有在其中放置任何东西,因此您传递的内容完全是任意的。稍微不同的代码会在这些地方留下完全不同的值,所以你得到不同的结果也就不足为奇了:

  • 如果这个值刚好是0,那也没关系;您可以将 NULL 作为 module 值传递。
  • 如果该值恰好指向未映射的内存,比如 1(如原始 C long/long long,而不是 PyLongObject),你应该得到一个段错误来自 incref 模块的尝试。
  • 如果该值恰好是指向内存中某个随机事物的指针,则 incref 将起作用,但会破坏该随机事物。它几乎可以做任何事情,但是在以后的某个任意时间点出现神秘的段错误几乎是它可以做的最不令人惊讶的事情。

同时,来自评论:

I am calling PyCFunction_NewEx because PyCFunction_New is a marco in the source code.

如果您使用的是 Python 2.3-2.6 或 3.0-3.2,那没问题。但是在以后的版本中,包括你说你正在使用的 3.5,CPython 特意将 PyCFunction_New 定义为一个函数,这样它就会出现在 API (甚至稳定 API,对于 3.x)。参见 3.5 例如:

/* undefine macro trampoline to PyCFunction_NewEx */
#undef PyCFunction_New

PyAPI_FUNC(PyObject *)
PyCFunction_New(PyMethodDef *ml, PyObject *self)
{
    return PyCFunction_NewEx(ml, self, NULL);
}

所以,你真的可以调用 PyCFunction_New