我正在尝试使用
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
与手写的
get_errno
不同,ctypes get_errno
不会访问 os errno
值,而是在某些函数调用后填充的私有副本。 文档状态:
[...] 一种 ctypes 机制,允许以安全的方式访问系统 errno 错误号。 ctypes 维护系统 errno 变量的线程本地副本;如果您调用使用
创建的外部函数,则函数调用之前的 errno 值将与 ctypes 私有副本交换,函数调用后会立即发生相同的情况。
use_errno=True
因此,
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
对于单个函数,您可以使用 函数原型 来代替。