Python之(18)ctypes使用-程序员宅基地

技术标签: python  # 编程语言  # Python开发  开发语言  

Python之ctypes(1)基础使用

author:Once Day date: 2023年12月6日

漫漫长路,才刚刚开始…

全系列文档查看:python基础_程序员宅基地

参考文档:

1. 概述
1.1 介绍

ctypes 是一个 Python 标准库,它提供了和 C 语言库交互的能力。利用 ctypes,你可以在 Python 中加载动态链接库(DLLs 或在 Unix-like 系统中的 shared objects),并且可以调用这些库中的函数。这使得Python可以使用已经编译好的代码,这通常是为了性能或者重用现有的C代码。

要使用 ctypes,首先需要导入该模块:

import ctypes

然后,你可以加载一个库,调用其中的函数,传递参数,以及获取返回值。

# 对于 Windows DLL
my_library = ctypes.WinDLL('mylibrary.dll')

# 对于 Unix-like 系统上的 shared object
my_library = ctypes.CDLL('libmylibrary.so')

要调用库中的函数,可以直接通过库对象访问函数,就像访问其属性一样:

result = my_library.my_function(1, 2)

但在调用之前,通常需要指定函数的参数类型和返回类型,以便 ctypes 可以正确地处理数据转换。

# 指定函数的参数类型
my_library.my_function.argtypes = (ctypes.c_int, ctypes.c_int)

# 指定返回类型
my_library.my_function.restype = ctypes.c_int

result = my_library.my_function(1, 2)

ctypes 提供了很多与 C 语言中对应的数据类型:

  • c_int, c_short, c_long, c_longlong, 整数类型。
  • c_uint, c_ushort, c_ulong, c_ulonglong, 无符号整数类型。
  • c_float, c_double, 浮点数类型。
  • c_char, c_wchar,字符类型。
  • c_void_p,void 指针类型。

ctypes 还允许你定义结构体和联合体,以及创建和操作指针:

# 定义结构体
class Point(ctypes.Structure):
    _fields_ = [('x', ctypes.c_int),
                ('y', ctypes.c_int)]

# 创建结构体的实例
point = Point(10, 20)

# 传递结构体的指针
my_library.some_function(ctypes.byref(point))

在使用 ctypes 调用外部函数时,错误处理非常重要。库函数可能会根据其定义返回错误代码,或者可能产生异常。你需要根据库的文档和函数声明来妥善处理这些情况。

1.2 优点和缺点

ctypes 库是 Python 编程语言中用于与 C 语言库交互的一个有力工具。

以下是 ctypes 的一些优点:

  1. 无需编写扩展模块ctypes 允许你直接从 Python 代码调用 C 库函数,无需编写包装器或者扩展模块。
  2. 标准库的一部分ctypes 随 Python 标准库提供,因此不需要额外安装。
  3. 跨平台ctypes 在多数平台上工作得很好,包括 Windows、Linux 和 macOS。
  4. 动态调用:可以在运行时动态加载库,不需要链接到固定的库。
  5. 易用性:对于简单的库,ctypes 可以很容易地使用。
  6. 灵活性ctypes 可以处理许多不同的数据类型,并且能够很好地处理指针、结构体和联合体。

以下是 ctypes 的一些缺点:

  1. 对于复杂API的封装可能很繁琐:如果 C 库功能复杂,那么使用 ctypes 进行封装可能会涉及大量的工作,特别是要正确处理数据结构和内存管理。
  2. 性能开销:虽然 ctypes 调用 C 函数比纯 Python 快,但它比使用 C API 编写的原生 Python 扩展模块慢,因为它在运行时必须解析和转换数据类型。
  3. 内存管理风险ctypes 的使用者负责处理所有内存管理的方面,这可能导致内存泄露和程序崩溃,尤其是如果没有正确地管理引用和生命周期。
  4. C接口的变动:如果 C 库的接口改变,需要更新 ctypes 的定义,这可能导致维护成本。
  5. 错误处理ctypes 不会像 CPython 扩展模块那样自动处理 C 代码中的错误,需要手动检查并处理错误。
  6. 类型安全性ctypes 是类型不安全的,如果调用者使用了错误的类型,可能会导致不可预知的行为,甚至崩溃。
  7. 调试困难:调试跨语言的问题比单一语言中的问题更复杂,尤其是涉及底层内存操作时。

ctypes 适合于轻量级别的编程场景,例如直接拿到未开源的C动态库,或者需要快速编写python脚本测试的场景。在Python本身就可以实现的情况下,应该优先使用Python自身的功能而不要使用操作系统提供的API接口,。

2. 实际使用

本节以Linux平台作为实验平台,关于Windows平台可以参考官方文档(更加复杂,但原理相差不大)。

2.1 载入动态链接库

ctypes在Linux平台可以导入cdll对象,在 Windows 系统中则可以导入windlloledll动态链接对象。

  • cdll加载使用标准cdecl调用约定导出函数的库。
  • windll使用stdcall调用约定调用函数。
  • oledll使用stdcall调用约定,并假定函数返回Windows HRESULT错误代码。 当函数调用失败时会使用错误代码自动引发OSError异常。

下面加载C标准库,并且操作其中的函数:

>>> from ctypes import *		# 导入ctypes模块
>>> libc = CDLL("libc.so.6")	# 加载动态库,创建CDLL的实例
>>> libc
<CDLL 'libc.so.6', handle 7fb249b0d3f0 at 0x7fb2492e5ae0>
>>>print(libc.rand())			# 这里输出一个随机数
1804289383

如上面所示,只要导入了动态库,其中导出的变量和函数可以随便使用。

需要注意,如果动态库之间存在依赖,比如下面动态库B依赖A,那么导入动态B之间,需要先导入A,否则会报存在未定义符号的错误。可使用ldd查看动态库的依赖关系:

ubuntu->python:$ sudo ldd /usr/local/lib/libyang.so
        linux-vdso.so.1 (0x00007fff363f6000)
        libpcre2-8.so.0 => /lib/x86_64-linux-gnu/libpcre2-8.so.0 (0x00007f07e201e000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f07e1df6000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f07e2249000)

当调用一个不存在的函数时,会直接报错,如下:

>>> libc.unknown
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.10/ctypes/__init__.py", line 387, in __getattr__
    func = self.__getitem__(name)
  File "/usr/lib/python3.10/ctypes/__init__.py", line 392, in __getitem__
    func = self._FuncPtr((name_or_ordinal, self))
AttributeError: /lib/x86_64-linux-gnu/libc.so.6: undefined symbol: unknown

如果遇到C动态库函数内存问题,导致coredump,那么可以使用faulthandler来打印调用栈错误,从而更好定位:

onceday->python:# python3 -q -X faulthandler
>>> import ctypes
>>> ctypes.string_at(0)
Fatal Python error: Segmentation fault

Current thread 0x00007f1f2d53d1c0 (most recent call first):
  File "/usr/lib/python3.10/ctypes/__init__.py", line 517 in string_at
  File "<stdin>", line 1 in <module>
Segmentation fault (core dumped)
2.2 C数据类型转换

有四类Python对象是可以自动转换为C函数参数,如下:

  • None 将作为 C NULL 指针传入。
  • 字节对象和Unicode字符串将作为指向包含其数据 (char*char_t*) 的内存块的指针传入。
  • Python 整数则作为平台默认的 C int 类型传入,它们的值会被截断以适应 C 类型的长度。

下面表格来自ctypes官方文档,ctypes 基础数据类型 :

ctypes 类型 C 类型 Python 类型
c_bool _Bool bool (1)
c_char char 单字符字节串对象
c_wchar wchar_t 单字符字符串
c_byte char int
c_ubyte unsigned char int
c_short short int
c_ushort unsigned short int
c_int int int
c_uint unsigned int int
c_long long int
c_ulong unsigned long int
c_longlong __int64 或 long long int
c_ulonglong unsigned __int64 或 unsigned long long int
c_size_t size_t int
c_ssize_t ssize_t或 Py_ssize_ int
c_time_t time_t int
c_float float float
c_double double float
c_longdouble long double float
c_char_p char* (以 NUL 结尾) 字节串对象或 None
c_wchar_p wchar_t* (以 NUL 结尾) 字符串或 None
c_void_p void* int 或 None

这些ctypes对象架起了一道桥梁,沟通Python类型值和C类型值,使用它们非常简单,如下:

(1) 使用合适的值和类型来初始化它们:

>>> c_int()
c_int(0)
>>> c_int(10)
c_int(10)
>>> c_int(9876543210) # 溢出的位被截断
c_int(1286608618)
>>> c_ushort(-1)
c_ushort(65535)
>>> c_ushort(1)
c_ushort(1)
>>> c_char_p("hello!") # c_char_p不能直接接受python字符串(为unicode字符串)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: bytes or integer address expected instead of str instance
>>> c_wchar_p("hello!")
c_wchar_p(139723023540784)
>>> c_char_p(b"hello!")
c_char_p(139723023824512)

(2) 创建Ctypes对象后可以更改它们的值:

>>> s = "Hello, World"
>>> c_s = c_wchar_p(s)
>>> print(c_s)
c_wchar_p(139723020918896)
>>> print(c_s.value)
Hello, World
>>> c_s.value = "new value"
>>> print(c_s.value)
new value
>>> print(c_s)
c_wchar_p(139723023824528)
>>> print(s)
Hello, World

对于指针对象,赋值时会改变指向的内存地址,而不是指向内存区域的数据,对于其他对象,则会改变其内存区域的值

对于直接引用Python字符串或者字节流的ctypes指针类型(c_char_p, c_wchar_pc_void_p等),不能将它们作为参数传递给会改变指针所指向内存的函数(Python的bytes对象是不可变的)。

这种情况下,需要使用create_string_buffer()函数,可以创建可变内容的内存块,并通过raw属性获取。示例如下:

>>> p_str = create_string_buffer(8) 
>>> print(sizeof(p_str), p_str.raw)
8 b'\x00\x00\x00\x00\x00\x00\x00\x00'
>>> p_str.value = b"hello!" # 通过value可以在创建对象后再赋值
>>> print(sizeof(p_str), p_str.raw)
8 b'hello!\x00\x00'
>>> p_str.value = b"hello world!" # 赋值超过大小,会触发错误
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: byte string too long

如果要创建一个包含 C 类型 wchar_t 的 unicode 字符的可变内存块,可以使用**create_unicode_buffer()**函数。

2.3 可变参数函数

对于可变参数函数,可以如下直接调用:

>>> libc = CDLL("libc.so.6")
>>> printf = libc.printf
>>> printf(b"hello, %s\n", b"ctypes")
hello, ctypes
14
>>> printf(b"hello, %s%s%s%s%s%s\n", b"c", b"t", b"y", b"p", b"e", b"s")
hello, ctypes
14

虽然大部分平台上通过调用ctypes调用可变参数函数和固定参数函数是一样的,但是针对部分特殊平台,可变函数调用约定有一些特殊。在这些平台上要求为常规、非可变函数参数指定 argtypes 属性:

>>> libc.printf.argtypes = [ctypes.c_char_p]
>>> print(printf.argtypes)
[<class 'ctypes.c_char_p'>]

指定该属性不会影响可移植性,所以建议总是为所有可变函数指定 argtypes

2.4 指定参数类型或者函数原型

这里使用printf来作为例子,尽管它是一个可变参数函数,这里主要说明如何自动转换参数为合适的Ctypes类型。

(1) 可以通过自定义 ctypes 参数转换方式来允许将你自己的类实例作为函数参数。

ctypes 会寻找 _as_parameter_ 属性并使用它作为函数参数。 属性必须是整数、字符串、字节串、ctypes 实例或者带有 _as_parameter_ 属性的对象:

>>> class Book:
...     def __init__(self, name, pages):
...             self._as_parameter_ = c_char_p(bytes(f'{name}-{pages}', "ascii"))
... 
>>> book = Book("New book", 66)
>>> book
<__main__.Book object at 0x7f3bb64d8790>
>>> libc = CDLL("libc.so.6")
>>> printf = libc.printf
>>> printf(b"Book: %s.\n", book)

如果你不想将实例数据存储在 _as_parameter_ 实例变量中,可以定义一个根据请求提供属性的property

(2) 指定选择函数的原型,通过原型参数,在执行C函数时,ctypes将能够实现自动转换功能。

>>> printf.argtypes = [c_char_p, c_char_p, c_int, c_double]
>>> printf(b"String '%s', Int %d, Double %f\n", b"Hi", 10, 2.2)
String 'Hi', Int 10, Double 2.200000
37
>>> printf(b"%d %d %d", 1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ctypes.ArgumentError: argument 2: <class 'TypeError'>: wrong type

指定数据类型可以防止不合理的参数传递(就像 C 函数的原型),并且会自动尝试将参数转换为需要的类型:

如果定义了自己的类并将其传递给函数调用,则必须为它们实现 from_param()类方法才能够在 argtypes序列中使用它们。 from_param() 类方法将接受传递给函数调用的 Python 对象,它应该进行类型检查或者其他必要的操作以确保这个对象是可接受的,然后返回对象本身、它的 _as_parameter_ 属性或在此情况下作为 C 函数参数传入的任何东西。

结果应该是整数、字符串、字节串、ctypes实例或具有 _as_parameter_ 属性的对象

>>> class Cstring:
...     @classmethod
...     def from_param(cls, pyobj):
...             return c_char_p(bytes(pyobj, "ascii"))
... 
>>> printf.argtypes = [Cstring]
>>> printf("Cstring\n")
Cstring
8
2.5 函数返回类型

默认情况下都会假定函数返回C int类型。 其他返回类型可通过设置函数对象的restype属性来指定。

time()的 C 原型是time_t time(time_t *),可以指定该函数的返回类型为c_ulong,如下:

>>> ctime = libc.time
>>> ctime.restype = c_ulong
>>> ctime.argtypes = [POINTER(c_ulong)]
>>> ctime(None)
1703168837

调用该函数时如果要将 NULL 指针作为第一个参数,可以使用 None

可以定义一个python回调函数,其在c函数调用后处理返回的参数,如下:

callable(result, func, arguments)
  • result是外部函数返回的结果,由 restype 属性指明。
  • func是外部函数对象本身,这样就允许重新使用相同的可调用对象来对多个函数进行检查或后续处理。
  • arguments是一个包含最初传递给函数调用的形参的元组,这样就允许对所用参数的行为进行特别处理。

然后将该函数赋值给函数对象的errcheck属性,如下:

>>> def check_error(result, func, args):
...     print(result)
...     print(func)
...     print(args)
...     return "Success"
... 
>>> ctime.errcheck = check_error
>>> ctime(None)
1703169973
<_FuncPtr object at 0x7f3bb50fe440>
(None,)
'Success'
2.6 传递指针参数

C函数接口往往会使用大量的指针参数,对于Python来说,指针隐藏在内部实现中,因此需要做一层转换,实现传递参数引用。

可以使用 byref() 函数传递参数引用(类似于指针传递),使用pointer()函数也能达到同样的效果,只不过pointer()需要更多步骤,因为它要先构造一个真实指针对象。所以在 Python 代码本身不需要使用这个指针对象的情况下,使用byref()效率更高。

>>> buf = create_string_buffer(32)
>>> libc.snprintf(byref(buf), 32, b"hello, %s!/n", b"world")
15
>>> print(buf)
<ctypes.c_char_Array_32 object at 0x7f3bb4f5e7c0>
>>> print(buf.value)
b'hello, world!/n'
2.7 结构体和联合体数据

结构体和联合必须派生自StructureUnion基类,这两个基类是在ctypes模块中定义的。 每个子类都必须定义_fields_属性。 _fields_必须是一个 2元组 的列表,其中包含一个字段名称和一个字段类型

type 字段必须是一个 ctypes 类型,比如 c_int,或者其他 ctypes 类型: 结构体、联合、数组、指针。

>>> from ctypes import *
>>> class POINT(Structure):
...     _fields_ = [("x", c_int),
...                 ("y", c_int)]
... 
>>> print(POINT.x)
<Field type=c_int, ofs=0, size=4>
>>> point = POINT(10, 20)
>>> print(point.x, point.y)
10 20
>>> point = POINT(y=5)
>>> print(point.x, point.y)
0 5

可以嵌套的构建复杂的结构体,如下:

>>> class RECT(Structure):
...     _fields_ = [("upperleft", POINT),
...                 ("lowerright", POINT)]
... 
>>> rc = RECT(point)
>>> print(rc.upperleft.x, rc.upperleft.y)
0 5
>>> print(rc.lowerright.x, rc.lowerright.y)
0 0

初始化方法一般如下两种:

>>> r = RECT(POINT(1, 2), POINT(3, 4))
>>> r = RECT((1, 2), (3, 4))

带位域的结构体一般不支持以值的方法传递给函数,建议使用指针传递值ctypes中的结构体和联合使用的是本地字节序。要使用非本地字节序,可以使用BigEndianStructure, LittleEndianStructure, BigEndianUnion, 和LittleEndianUnion作为基类。这些类不能包含指针字段。如下所示:

>>> class Int(Structure):
...     _fields_ = [("first_16", c_int, 16),
...                 ("second_16", c_int, 16)]
... 
>>> print(Int.first_16)
<Field type=c_int, ofs=0:0, bits=16>
>>> print(Int.second_16)
<Field type=c_int, ofs=0:16, bits=16>
2.8 数组数据

数组是包含多个类型相同元素的集合,在Python中,可以直接使用类型乘以数目来创建数组:

>>> TenPointsArrayType = POINT * 10
>>> print(TenPointsArrayType)
<class '__main__.POINT_Array_10'>

下面是一个关于结构体数组的例子,对于其他类型而言,操作是一样的:

>>> from ctypes import *
>>> class Book(Structure):
...     _fields_ = ("name", c_wchar_p), ("pages", c_int)
... 
>>> class Books(Structure):
...     _fields_ = [("num", c_int),
...                 ("data", Book * 4)]
... 
>>> print(len(Books().data))
4
>>> mybooks = Books(2, (("C book", 100), ("Python book", 88)))
>>> print(mybooks.data[0])
<__main__.Book object at 0x7f3bb4f5e840>
>>> print(mybooks.data[0].name)
C book
>>> print(mybooks.data[1].name)
Python book
>>> print(mybooks.data[2].name)
None
2.9 指针类型

可以将ctypes类型数据传入pointer()函数创建指针:

>>> from ctypes import *
>>> n = c_int(1888)
>>> n.value
1888
>>> pn = pointer(n)
>>> pn
<__main__.LP_c_int object at 0x7f3bb4f5e840>
>>> pn.contents
c_int(1888)

ctypes 并没有OOR(返回原始对象), 每次访问这个属性时都会构造返回一个新的相同对象:

>>> pn.contents is pn.contents
False
>>> a = pn.contents
>>> b = pn.contents
>>> id(a)
139894415813184
>>> id(b)
139894415812800

通过对contents属性进行赋值,可以将该指针指向另外一个ctypes对象的地址。

>>> pn.contents = c_int(1999)
>>> pn.contents
c_int(1999)

也可以通过数组下标的方式访问和改变指针的值:

>>> pn[0]
1999
>>> pn[0] = 2000
>>> pn[0]
2000

这里需要注意,ctypes没有判断该指针指向的范围,因此如果使用超过0的索引,那么可能造成内存覆写,和C指针一样,需要使用者对内存使用进行负责

解引用指针时,如果是NULL指针,ctypes会检查该错误,并且报出ValueError: NULL pointer access错误,但是非零无效的指针值,ctypes无法检查,这会造成Python崩溃。

2.10 类型转换

如果在函数的argtypes列表中有POINTER(c_int)或在结构体定义中将其用作成员字段的类型,则只接受完全相同类型的实例。 此规则也有一些例外情况,在这些情况下 ctypes 可以接受其他对象。 例如,可以传入兼容的数组实例而不是指针类型。

class Bar(Structure):
    _fields_ = [("count", c_int), ("values", POINTER(c_int))]

bar = Bar()
bar.values = (c_int * 3)(1, 2, 3)
bar.count = 3
for i in range(bar.count):
    print(bar.values[i])

此外,如果函数参数在argtypes中明确声明为指针类型 (如POINTER(c_int)),则可以向函数传递所指向的类型的对象 (在本例中为 c_int)。 在这种情况下,ctypes将自动应用所需的byref()转换。

在ctypes中,可以使用cast将一个类型强制转换为另一个,即C语言中的强制类型转换功能

>>> a = (c_char * 8)()
>>> a
<__main__.c_char_Array_8 object at 0x7f3bb4f5eb40>
>>> cast(a, POINTER(c_int))
<__main__.LP_c_int object at 0x7f3bb4f5ea40>

C语言声明时,可以定义不完整类型,即还没有定义成员的结构体、联合或者数组,通常用于前置声明,然后在后面定义

struct cell; /* forward declaration */

struct cell {
    char *name;
    struct cell *next;
};

如果Python中也这么直接定义,就会出现报错,显示模块未定义:

>>> class cell(Structure):
...     _fields_ = [("name", c_char_p),
...                 ("next", POINTER(cell))]
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in cell
NameError: name 'cell' is not defined. Did you mean: 'cdll'?

因此,在Python中,需要先定义Classs,再指定类的_fields_属性:

from ctypes import *
class cell(Structure):
    pass

cell._fields_ = [("name", c_char_p),
                 ("next", POINTER(cell))]
2.11 C函数指针类型

对于Python而言,还需要构建能在C函数中被调用的Python函数,当然,无法直接调用,而是由ctypes和ffi构建C到Python的转换层来实现。

ctypes允许创建一个指向 Python 可调用对象的C函数。它们有时候被称为回调函数

首先,需要为回调函数创建一个类,这个类知道调用约定,包括返回值类型以及函数接收的参数类型及个数:

  • CFUNCTYPE()工厂函数使用 cdecl 调用约定创建回调函数类型。
  • 在Windows上, WINFUNCTYPE()工厂函数使用stdcall调用约定为回调函数创建类型。

CFUNCTYPE()WINFUNCTYPE()函数的第一个参数是返回值类型,回调函数的参数类型作为剩余参数。

下面这个例子是官方文档的经典例子,即使用qsort()函数:

IntArray5 = c_int * 5
ia = IntArray5(5, 1, 7, 33, 99)
qsort = libc.qsort
qsort.restype = None

qsort() 被调用时必须传入一个指向要排序的数据的指针、数据数组中的条目数、每条目的大小以及一个指向比较函数即回调函数的指针。回调函数将附带两个指向条目的指针进行调用,如果第一个条目小于第二个条目则它必须返回一个负整数,如果两者相等则返回零,在其他情况下则返回一个正整数。

CMPFUNC = CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))

def py_cmp_func(a, b):
    print("py_cmp_func", a[0], b[0])
    return a[0] - b[0]

cmp_func = CMPFUNC(py_cmp_func)

执行结果如下:

>>> qsort(ia, len(ia), sizeof(c_int), CMPFUNC(py_cmp_func))
py_cmp_func 5 1
py_cmp_func 33 99
py_cmp_func 7 33
py_cmp_func 1 7
py_cmp_func 5 7

特别注意,需要维持CFUNCTYPE()对象的引用周期与它们在 C 代码中的使用期一样长。ctypes不会确保这一点,如果不这样做,它们可能会被垃圾回收,导致程序在执行回调函数时发生崩溃

因此,推荐使用装饰器语法来实现函数转换,这样可以自动保持CFUNCTYPE对象生命周期:

@CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))
def py_cmp_func(a, b):
    print("py_cmp_func", a[0], b[0])
    return a[0] - b[0]

如果回调函数在Python之外的另外一个线程使用(比如,外部代码调用这个回调函数), ctypes 会在每一次调用上创建一个虚拟 Python线程。这个行为在大多数情况下是合理的,但也意味着如果有数据使用 threading.local方式存储,将无法访问,就算它们是在同一个C线程中调用的。

2.12 读取动态库中的全局变量

某些共享库不仅会导出函数,还会导出变量,ctypes也提供了接口用于读取动态库中的全局变量,即通过类型的 in_dll()类方法访问这样的值。

in_dll(library, name)

此方法返回一个由共享库导出的ctypes类型。name为导出数据的符号名称,library为所加载的共享库。

下面以libc库中的程序名字全局变量来示例,即extern char *program_invocation_short_name*,基于argv[0]的值。

>>> name = c_char_p.in_dll(libc, "program_invocation_short_name")
>>> name
c_char_p(140736709220096)
>>> print(name.value)
b'python3'
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Once_day/article/details/135143218

智能推荐

python图书馆图书借阅管理系统django项目源码含文档ppt_基于python图书管理系统的ppt-程序员宅基地

文章浏览阅读1k次。图书馆图书借阅管理系统能做到的不仅是大大简化管理员的信息管理工作,在提高图书馆管理效率的同时还能缩减开支,更能在数字化的平面网络上将图书馆管理最好的一面展示给客户和潜在客户,而这个系统在带给图书馆管理全新用户信息统计和分类的同时,还成为日后图书馆管理制定管理方式的重要数据参考。过程永远比结果重要。毕业设计是大学生活中最为浓墨重彩的一笔,在这个过程中不仅学到更为全面的书本和实践知识,更让我感受到了浓浓的同窗之情及师生情本python+django+vue+Elementui+mysql系统可以定制,采用py_基于python图书管理系统的ppt

Android Q Init进程解析 rc文件的流程分析_output/vendor/etc/init/qvrd vndr.rc: 23: nvalid ke-程序员宅基地

文章浏览阅读1.7k次,点赞5次,收藏10次。init进程是Android系统在内核启动完毕之后,启动的第一个进程。这个进程会创建运行Android上传所需要的各种运行环境。这篇博文主要分析 init进程具体是如何进行 init.rc 以及其他的rc文件的。..._output/vendor/etc/init/qvrd vndr.rc: 23: nvalid keyword 'writepid

【SQL】在千万级的数据库查询中,如何提高效率?_千万级的数据sql怎样快速查询-程序员宅基地

文章浏览阅读1.6k次。文章目录千万级的数据库查询中,如何提高效率?数据库设计方面SQL语句方面Java方面千万级的数据库查询中,如何提高效率?数据库设计方面1.对查询进行优化,应尽量避免全表扫描,首先应考虑在where及order by涉及的列上建立索引。2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:select id from t where num is null可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:select i_千万级的数据sql怎样快速查询

WINCC无法激活项目时,如何进行软件修复?_该计算机上没有激活的wincc项目-程序员宅基地

文章浏览阅读6.2k次。具体原因:电脑的计算机名称因特殊原因进行更改,更改后wincc项目便无法再次打开,每次打开wincc项目,项目一直显示正在打开,具体修复措施如下:一:先将计算机名称改回到原来名称。打开控制面板,显示小图标,找到管理工具,再找到事件查看器。在事件查看器可找到修改信息。二:打开计算机,找到Rest_Wincc文件。具体文件地址如下:C:\Program Files (x86)\SIEMEN..._该计算机上没有激活的wincc项目

Base64编码解码_base64解码-程序员宅基地

文章浏览阅读3.7k次,点赞3次,收藏4次。Base64 是一种常用的编码方式,用于将二进制数据转换为可打印的 ASCII 字符串。它的主要特点如下:1. 字符集:Base64 使用了 64 个字符来表示数据,包括大小写字母(A-Z, a-z)、数字(0-9)以及两个特殊字符(+ 和 /)。2. 填充字符:Base64 编码后的字符串长度通常不会与原始数据长度完全对齐。为了保持长度整齐,Base64 在末尾添加一个或两个 `=` 号作为填充字符。3. 编码方式:Base64 编码将每 3 个字节作为一组,转换为 4 个 Base64 字符。_base64解码

html 页面不能选中状态,设置checkbox不能选中,复选框不能选中_html/css_WEB-ITnose-程序员宅基地

文章浏览阅读1.9k次。Web开发:设置复选框的只读效果在Web开发中,有时候需要显示一些复选框(checkbox),表明这个地方是可以进行勾选操作的,但是有时候是只想告知用户"这个地方是可以进行勾选操作的"而不想让用户在此处勾选(比如在信息展示页面),这时候就需要将复选框设置成只读的效果。提到只读,很容易想到使用readonly属性,但是对于复选框来说,这个属性和期望得到的效果是有差别的。原因在于readonly属性关..._为什么在hbuilder中两个复选框勾选不了

随便推点

Flash服务端常见架构方案_flash项目技术方案-程序员宅基地

文章浏览阅读3.7k次。目前有以下几种网页游戏服务器:利用.asc通信文件访问其他高级语言,比如java,vc,.net等访问数据库1)FMS(Flash Media Server)用AS2.0或者AS1.0来构建服务器端的,而客户端可以用AS3.0。在视频方面比较有优势,但是一般现在要结合其他语言开发比如NET类的。flash聊天室啊,在线视频会议啊啊, 网络_flash项目技术方案

机器学习、深度学习、人工智能三者之间究竟是什么关系?_ai 机器学习 深度学习关系-程序员宅基地

文章浏览阅读1.6k次。人工智能(Artificial Intelligence):人工智能是一个广泛的概念,指的是使计算机系统具备像人类一样的智能和能力。人工智能涵盖了包括机器学习和深度学习在内的各种方法和技术,旨在让计算机能够感知、理解、推理、学习和解决问题。人工智能的目标是模拟和实现人类智能的各个方面,以改善生活、提高效率和解决复杂的问题。机器学习(Machine Learning):机器学习是一种人工智能的方法和技术,旨在使计算机系统能够从数据中学习和改进,而无需明确编程。_ai 机器学习 深度学习关系

spreadtrum展信平台加密Secure boot流程_spreadtrumtools-程序员宅基地

文章浏览阅读3.2k次,点赞4次,收藏13次。1. Secure boot概述本文档主要是secure boot方案的介绍和说明,其内容会涵盖以下方面:secure boot的目的和介绍、技术方案的描述、PC端签名工具和Image download&amp;update工具的使用以及产线实施所需要做的准备工作和注意事项等。1.1. 需求与目的目前,非授权更改甚至替换手机原版操作系统中固有软件或者操作系统的软件技术手段层出不穷,se..._spreadtrumtools

最长上升子序列(LIS)问题的四种求解方法(JavaScript版,自用)_js 最长递增子序列算法-程序员宅基地

文章浏览阅读601次,点赞11次,收藏11次。最长上升子序列(LIS)问题的四种求解方法:DP,二分+贪心,dfs+回溯,树状数组优化DP_js 最长递增子序列算法

手机html己停用怎么办,iphone手机出现已停用请五分钟再试怎么办-程序员宅基地

文章浏览阅读1.1w次。iphone手机出现已停用请五分钟再试怎么办当苹果手机出现了iphone已停用请五分钟再试怎么办呢,下面小编介绍一下。具体如下:1. 当解锁苹果手机时,连续输错了四次密码后,会出现1分钟后才能继续输入密码2. 如果1分钟后,输入的密码还是错误的,那么手机将被锁定5分钟3. 如果还是输入错误密码,手机将会锁定更长的时间,最后会被停用4. 所以,为了避免手机被锁定或者停用的情况,一定要输入正确的密码。..._iphone不可用请5分钟后再试

Error executing Maven. 2 problems were encountered while building the effective settings-程序员宅基地

文章浏览阅读1.8w次。idea在启动SpringBoot项目的时候,报了一个Error executing Maven. 2 problems were encountered while building the effective settings错误解决方式:maven的配置文件中有多余的标签需要删除..._2 problems were encountered while building the effective settings

推荐文章

热门文章

相关标签