Python之(18)ctypes使用

发布时间:2023年12月22日

Python之ctypes(1)基础使用

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

漫漫长路,才刚刚开始…

全系列文档查看:python基础_CSDN博客

参考文档:

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_Boolbool (1)
c_charchar单字符字节串对象
c_wcharwchar_t单字符字符串
c_bytecharint
c_ubyteunsigned charint
c_shortshortint
c_ushortunsigned shortint
c_intintint
c_uintunsigned intint
c_longlongint
c_ulongunsigned longint
c_longlong__int64 或 long longint
c_ulonglongunsigned __int64 或 unsigned long longint
c_size_tsize_tint
c_ssize_tssize_t或 Py_ssize_int
c_time_ttime_tint
c_floatfloatfloat
c_doubledoublefloat
c_longdoublelong doublefloat
c_char_pchar* (以 NUL 结尾)字节串对象或 None
c_wchar_pwchar_t* (以 NUL 结尾)字符串或 None
c_void_pvoid*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'
文章来源:https://blog.csdn.net/Once_day/article/details/135143218
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。