【问题标题】:What happens when NULL pointers are returned from "foreign functions" using ctypes in python?在 python 中使用 ctypes 从“外部函数”返回 NULL 指针时会发生什么?
【发布时间】:2015-04-15 05:29:40
【问题描述】:

当使用 ctypes 包装 C 函数时,当方法返回指向映射到从 ctypes.Structure 派生的 python 类的结构的 NULL 指针时会发生什么?此外,当传递给方法的有效指针设置为 NULL 值时会发生什么?以下测试将帮助我们找出答案:

//python_ctypes_test.c
#include <stdlib.h>

struct test_struct {
    int a;
    int b;
    int c;
    int d;
} typedef test_struct;

test_struct* get_test_struct(const int valid) {
    test_struct* newStruct = 0;
    if (valid > 0) {
        newStruct = malloc(sizeof *newStruct);
        newStruct->a = 1;
        newStruct->b = 2;
        newStruct->c = 3;
        newStruct->d = 4;
    }
    return newStruct;
}

void get_two_floats(const int valid, float* float1, float* float2) {
    if (valid > 0) {
        *float1 = 1.1f;
        *float2 = 2.2f;
    } else {
        float1 = 0;
        float2 = 0;
    }
}

#python_ctypes_test.py
import ctypes
import ctypes.util

dll = ctypes.CDLL('libpython_ctypes_test.so')

class _test_struct(ctypes.Structure):
    _fields_ = [("a", ctypes.c_int),
                ("b", ctypes.c_int),
                ("c", ctypes.c_int),
                ("d", ctypes.c_int)]

    def wrap(self, something):
        self.a,self.b,self.c,self.d = something

    def unwrap(self):
        part1 = self.a,self.b
        part2 = self.c,self.d
        return part1, part2

dll.get_test_struct.restype = ctypes.POINTER(_test_struct)
dll.get_test_struct.argtypes = [ctypes.c_int]
def py_get_test_struct(valid):
    return dll.get_test_struct(valid).contents.unwrap()

def py_get_test_struct_safe(valid):
    testStructPtr = dll.get_test_struct(valid)
    if testStructPtr: 
        return testStructPtr.contents.unwrap()
    else:
        return None

dll.get_two_floats.restype = None
dll.get_two_floats.argtypes = [ctypes.c_int, 
        ctypes.POINTER(ctypes.c_float), ctypes.POINTER(ctypes.c_float)]
def py_get_two_floats(valid):
    float1_value = ctypes.c_float(0)
    float1 = ctypes.pointer(float1_value)
    float2_value = ctypes.c_float(0)
    float2 = ctypes.pointer(float2_value)

    dll.get_two_floats(valid, float1, float2)

    return float1_value.value, float2_value.value

print py_get_two_floats(True)
print py_get_two_floats(False)
print py_get_test_struct_safe(True)
print py_get_test_struct_safe(False)
print py_get_test_struct(True)
print py_get_test_struct(False)

【问题讨论】:

  • 您认为ctypes pointers tutorial 中缺少什么吗?它讨论了它们的布尔值并演示了ValueError。
  • 我想我想看一节讨论 ctypes 如何处理从 C 函数返回的 NULL 指针和由 C 函数更改为 NULL 指针的引用传递的指针。我不清楚这些点是直接解决的。
  • 它确实讨论了NULL 指针;它们是如何创建的并不重要。 C 函数在寄存器中返回一个整数值(例如 x64 中的 rax),并且 ctypes 从该值创建一个指针实例,该值可能为 0。对于第二个问题,您没有通过引用传递指针。您正在按值传递它们。
  • 通常您只需使用float1 = ctypes.c_float() 并将引用作为ctypes.byref(float1) 传递,而不是创建指针实例。这是传递对浮点值的引用,但地址本身是按值传递的,无论是在堆栈上还是在寄存器中(例如,x64 中的寄存器 rcx)。

标签: python c ctypes


【解决方案1】:

事实证明,开发人员不必关心指针的地址,它已被传递给函数,正在发生变化。

print py_get_two_floats(True)
  (1.100000023841858, 2.200000047683716)

print py_get_two_floats(False)
  (0.0, 0.0)

在第二种情况下,值与其初始化状态保持不变。这通常是正确的。如果 float1_value 和 float2_value 被初始化为 10 和 20。这些值也将保持不变。

正如@eryksun 指出的那样:

float1 和 float2 是包含地址的局部变量 两个花车。您可以将该值设置为 0 或任何其他地址,但 那只是改变一个局部变量。那不一样 取消引用指针(例如 *float1)以访问浮点 地址的值。

在返回值的情况下,事情的处理方式不同。返回的对象的计算结果为真(非空)或假(空)。

print py_get_test_struct_safe(True)
  ((1, 2), (3, 4))

print py_get_test_struct_safe(False)
  None

尝试访问 NULL 指针会导致 ValueError 异常。

print py_get_test_struct(True)
  ((1, 2), (3, 4))

print py_get_test_struct(False)
Traceback (most recent call last):
  File "python_ctypes.py", line 52, in <module>
    print py_get_test_struct(False)
  File "python_ctypes.py", line 24, in py_get_test_struct
    return dll.get_test_struct(valid).contents.unwrap()
ValueError: NULL pointer access

【讨论】:

  • 这应该是基础 C 编程的课程吗? (1)float1是get_two_floats中的一个局部变量,所以当然设置为0在调用者的上下文中是没有效果的。 (2) 关于访问NULL指针,你可以简单地使用ctypes.POINTER(_test_struct)().contents来演示ValueError。
  • 我分享了这个例子,因为我正在调试使用 pyGLFW(GLFW C 库的包装器)的代码。如果在我演示的两种情况下 C 代码返回 null,我对 python 会返回什么有疑问。由于我找不到一个很好的例子来回答这个问题,所以我决定发布它。
  • float1 和 float2 是包含两个浮点地址的局部变量。您可以将该值设置为 0 或任何其他地址,但这只是更改局部变量。这与取消引用指针(例如*float1)以访问地址处的浮点值不同。
  • 谢谢,我现在明白我的错误了。你是对的,float1 和 float2 在 C 调用之后永远不会为 NULL。我误读了我正在测试的 API。我将修改我的答案以反映这一点。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多