如何(正确)使用 `ctypes.get_errno()`?

问题描述 投票:0回答:1

我正在尝试使用

ctypes
测试一些二进制库,我的一些测试涉及
errno

因此,我尝试检索它以检查错误情况处理,但是当尝试使用

ctypes.get_errno()
时,我奇怪地得到
0
作为 errno(“成功”),这不是我所期望的。

为什么会出现这种情况?

ctypes.get_errno()
真的可靠吗?

  • test.py
#!/usr/bin/env python3

import os
import ctypes
import errno

libc = ctypes.cdll.LoadLibrary("libc.so.6")
libc.write.restype = ctypes.c_ssize_t
libc.write.argtypes = ctypes.c_int, ctypes.c_void_p, ctypes.c_size_t

TMP_FILE = "/tmp/foo"

def main():
    fd: int
    errno: int = 0

    fd = os.open(TMP_FILE, os.O_RDONLY | os.O_CREAT)
    if fd == -1:
        errno = ctypes.get_errno()
        print(strerror(errno))

    if (not errno and libc.write(fd, "foo", 3) == -1):
        errno = ctypes.get_errno()
        print(f"ERRNO: {errno}")
        print(os.strerror(errno))

    os.close(fd);
    os.remove(TMP_FILE)

    if errno:
        raise OSError(errno, os.strerror(errno))


if __name__ == "__main__":
    main()

  • 输出:
$ ./test.py
ERRNO: 0
Success

注意:我已经从 另一篇文章下的答案找到了解决方法(请参阅下面的 MRE),但我想了解

ctypes.get_errno()
发生了什么。

  • test_with_workaround.py
#!/usr/bin/env python3

import os
import ctypes

libc = ctypes.cdll.LoadLibrary("libc.so.6")
libc.write.restype = ctypes.c_ssize_t
libc.write.argtypes = ctypes.c_int, ctypes.c_void_p, ctypes.c_size_t

TMP_FILE = "/tmp/foo"

_get_errno_loc = libc.__errno_location
_get_errno_loc.restype = ctypes.POINTER(ctypes.c_int)

def get_errno() -> int:
    return _get_errno_loc()[0]

def main():
    fd: int
    errno: int = 0

    fd = os.open(TMP_FILE, os.O_RDONLY | os.O_CREAT)
    if fd == -1:
        errno = get_errno()
        print(strerror(errno))

    if (not errno and libc.write(fd, "foo", 3) == -1):
        errno = get_errno()
        print(f"ERRNO: {errno}")
        print(os.strerror(errno))

    os.close(fd);
    os.remove(TMP_FILE)

    if errno:
        raise OSError(errno, os.strerror(errno))


if __name__ == "__main__":
    main()

  • 输出:
$ ./test_with_workaround.py
ERRNO: 9
Bad file descriptor
Traceback (most recent call last):
  File "/mnt/nfs/homes/vmonteco/Code/MREs/MRE_python_fdopen_cause_errno/simple_python_test/./test_with_workaround.py", line 41, in <module>
    main()
  File "/mnt/nfs/homes/vmonteco/Code/MREs/MRE_python_fdopen_cause_errno/simple_python_test/./test_with_workaround.py", line 37, in main
    raise OSError(errno, os.strerror(errno))
OSError: [Errno 9] Bad file descriptor
python ctypes errno
1个回答
0
投票

与手写的

get_errno
不同,ctypes
get_errno
不会访问 os
errno
值,而是在某些函数调用后填充的私有副本。 文档状态:

[...] 一种 ctypes 机制,允许以安全的方式访问系统 errno 错误号。 ctypes 维护系统 errno 变量的线程本地副本;如果您调用使用

use_errno=True
创建的外部函数,则函数调用之前的 errno 值将与 ctypes 私有副本交换,函数调用后会立即发生相同的情况。

因此,

ctypes.get_errno()
永远不会获得可能被设置为
os.open
副作用的错误。但是您可以(并且可能应该)将它用于通过 ctypes 调用的函数。但您需要将
use_errno
设置为
True

use_errno
是函数定义创建的参数。当您像使用
mylib.myfunc
一样使用
libc.write
语法时,函数创建是隐式的,并从库加载器继承一些默认值。此处使用
ctypes.cdll
,将
use_errno
设置为
False
。您可以通过更明确地加载库来进行更改:

libc = ctypes.CDll("libc.so.6", use_errno=True)

请注意,这会将 use_errno (以及相关的开销)应用于您想要使用的所有函数

use_errno
对于单个函数,您可以使用 函数原型 来代替。

© www.soinside.com 2019 - 2024. All rights reserved.