扩展与嵌入 Python:从 C 语言到 Python 的无缝衔接
1. 宏的使用示例
以下示例展示了一些宏的使用:
PyObject *py_wrapper(PyObject *self, PyObject *args) {
...
PyArg_ParseTuple(args, ...)
Py_BEGIN_ALLOW_THREADS
result = run_long_calculation(args);
Py_END_ALLOW_THREADS
...
return Py_BuildValue(fmt,result);
}
2. 嵌入 Python 解释器
Python 解释器可以嵌入到 C 应用程序中。嵌入后,Python 解释器作为一个编程库运行,C 程序可以初始化解释器、运行脚本和代码片段、加载库模块以及操作 Python 实现的函数和对象。
2.1 嵌入模板
以下是一个简单的 C 程序,展示了最基本的嵌入方式:
#include <Python.h>
int main(int argc, char **argv) {
Py_Initialize();
PyRun_SimpleString("print('Hello World')");
Py_Finalize();
return 0;
}
在这个例子中,解释器被初始化,一个简短的脚本作为字符串被执行,然后解释器被关闭。在进一步操作之前,先让这个示例正常工作通常是个好主意。
2.2 编译和链接
在 UNIX 上编译嵌入式解释器时,代码必须包含
"Python.h"
头文件,并链接解释器库,如
libpython2.6.a
。头文件通常位于
/usr/local/include/python2.6
,库文件通常位于
/usr/local/lib/python2.6/config
。在 Windows 上,需要在 Python 安装目录中找到这些文件。需要注意的是,解释器可能依赖于其他库,在链接时需要包含这些库。这通常与平台相关,并且取决于 Python 在机器上的配置,可能需要进行一些调整。
2.3 基本解释器操作和设置
以下函数用于设置解释器并运行脚本:
-
int PyRun_AnyFile(FILE *fp, char *filename)
:如果
fp
是交互式设备(如 UNIX 中的 tty),则调用
PyRun_InteractiveLoop()
;否则,调用
PyRun_SimpleFile()
。
filename
是输入流的名称,当解释器报告错误时会显示该名称。如果
filename
为
NULL
,则使用默认字符串
"???"
作为文件名。
-
int PyRun_SimpleFile(FILE *fp, char *filename)
:与
PyRun_SimpleString()
类似,不同之处在于程序从文件
fp
中读取。
-
int PyRun_SimpleString(char *command)
:在解释器的
__main__
模块中执行
command
。成功返回 0,发生异常返回 -1。
-
int PyRun_InteractiveOne(FILE *fp, char *filename)
:执行单个交互式命令。
-
int PyRun_InterativeLoop(FILE *fp, char *filename)
:以交互式模式运行解释器。
-
void Py_Initialize()
:初始化 Python 解释器。在使用 C API 中的其他函数之前,应调用此函数,但
Py_SetProgramName()
、
PyEval_InitThreads()
、
PyEval_ReleaseLock()
和
PyEval_AcquireLock()
除外。
-
int Py_IsInitialized()
:如果解释器已初始化,则返回 1;否则返回 0。
-
void Py_Finalize()
:通过销毁自调用
Py_Initialize()
以来创建的所有子解释器和对象来清理解释器。通常,此函数会释放解释器分配的所有内存。但是,循环引用和扩展模块可能会导致内存泄漏,此函数无法回收这些泄漏的内存。
-
void Py_SetProgramName(char *name)
:设置通常在
sys
模块的
argv[0]
参数中找到的程序名称。此函数应仅在
Py_Initialize()
之前调用。
-
char *Py_GetPrefix()
:返回已安装的与平台无关的文件的前缀,与
sys.prefix
中的值相同。
-
char *Py_GetExecPrefix()
:返回已安装的与平台相关的文件的执行前缀,与
sys.exec_prefix
中的值相同。
-
char *Py_GetProgramFullPath()
:返回 Python 可执行文件的完整路径名。
-
char *Py_GetPath()
:返回默认的模块搜索路径。路径作为一个字符串返回,由目录名组成,目录名之间用与平台相关的分隔符分隔(UNIX 上为
:
,DOS/Windows 上为
;
)。
-
int PySys_SetArgv(int argc, char **argv)
:设置用于填充
sys.argv
值的命令行选项。此函数应仅在
Py_Initialize()
之前调用。
3. 从 C 访问 Python
虽然有多种方法可以从 C 访问解释器,但在嵌入时,最常见的四个基本任务是:
- 导入 Python 库模块(模拟
import
语句)
- 获取模块中定义的对象的引用
- 调用 Python 函数、类和方法
- 访问对象的属性(数据、方法等)
所有这些操作都可以使用 Python C API 中定义的基本操作来完成:
-
PyObject *PyImport_ImportModule(const char *modname)
:导入模块
modname
并返回关联模块对象的引用。
-
PyObject *PyObject_GetAttrString(PyObject *obj, const char *name)
:从对象中获取属性,与
obj.name
相同。
-
int PyObject_SetAttrString(PyObject *obj, const char *name, PyObject *value)
:在对象上设置属性,与
obj.name = value
相同。
-
PyObject *PyEval_CallObject(PyObject *func, PyObject *args)
:使用参数
args
调用
func
。
func
是一个 Python 可调用对象(函数、方法、类等),
args
是一个参数元组。
-
PyObject *PyEval_CallObjectWithKeywords(PyObject *func, PyObject *args, PyObject *kwargs)
:使用位置参数
args
和关键字参数
kwargs
调用
func
。
func
是一个可调用对象,
args
是一个元组,
kwargs
是一个字典。
以下示例展示了如何从 C 调用并使用
re
模块的各个部分。该程序打印出从标准输入读取的所有包含用户提供的 Python 正则表达式的行:
#include "Python.h"
int main(int argc, char **argv) {
PyObject *re;
PyObject *re_compile;
PyObject *pat;
PyObject *pat_search;
PyObject *args;
char buffer[256];
if (argc != 2) {
fprintf(stderr,"Usage: %s pattern\n",argv[0]);
exit(1);
}
Py_Initialize();
/* import re */
re = PyImport_ImportModule("re");
/* pat = re.compile(pat,flags) */
re_compile = PyObject_GetAttrString(re,"compile");
args = Py_BuildValue("(s)", argv[1]);
pat = PyEval_CallObject(re_compile, args);
Py_DECREF(args);
/* pat_search = pat.search - bound method*/
pat_search = PyObject_GetAttrString(pat,"search");
/* Read lines and perform matches */
while (fgets(buffer,255,stdin)) {
PyObject *match;
args = Py_BuildValue("(s)", buffer);
/* match = pat.search(buffer) */
match = PyEval_CallObject(pat_search,args);
Py_DECREF(args);
if (match != Py_None) {
printf("%s",buffer);
}
Py_XDECREF(match);
}
Py_DECREF(pat);
Py_DECREF(re_compile);
Py_DECREF(re);
Py_Finalize();
return 0;
}
在任何嵌入代码中,正确管理引用计数至关重要。特别是,需要减少从 C 创建或作为函数求值结果返回给 C 的任何对象的引用计数。
4. 将 Python 对象转换为 C
嵌入式使用解释器的一个主要问题是将 Python 函数或方法调用的结果转换为合适的 C 表示形式。一般来说,需要提前确切知道操作将返回哪种类型的数据。遗憾的是,没有像
PyArg_ParseTuple()
这样的高级便利函数来转换单个对象值。但是,以下列出了一些低级转换函数,只要确切知道正在处理的 Python 对象类型,这些函数就可以将一些基本的 Python 数据类型转换为适当的 C 表示形式:
| Python 到 C 的转换函数 | 说明 |
| — | — |
|
long PyInt_AsLong(PyObject *)
| 将 Python 对象转换为
long
类型 |
|
long PyLong_AsLong(PyObject *)
| 将 Python 对象转换为
long
类型 |
|
double PyFloat_AsDouble(PyObject *)
| 将 Python 对象转换为
double
类型 |
|
char *PyString_AsString(PyObject *) (Python 2 only)
| 将 Python 对象转换为
char *
类型(仅适用于 Python 2) |
|
char *PyBytes_AsString(PyObject *) (Python 3 only)
| 将 Python 对象转换为
char *
类型(仅适用于 Python 3) |
对于更复杂的类型,需要参考 C API 文档(http://docs.python.org/c-api)。
5. ctypes 模块
ctypes
模块使 Python 能够访问 DLL 和共享库中定义的 C 函数。虽然需要了解一些底层 C 库的详细信息(名称、调用参数、类型等),但可以使用
ctypes
访问 C 代码,而无需编写 C 扩展包装代码或使用 C 编译器进行编译。
ctypes
是一个功能强大的库模块,具有许多高级功能。这里介绍其基本部分。
5.1 加载共享库
以下类用于加载 C 共享库并返回表示其内容的实例:
-
CDLL(name [, mode [, handle [, use_errno [, use_last_error]]]])
:表示标准 C 共享库的类。
name
是库的名称,如
'libc.so.6'
或
'msvcrt.dll'
。
mode
提供标志,用于确定库的加载方式,并传递给 UNIX 上的底层
dlopen()
函数。可以设置为
RTLD_LOCAL
、
RTLD_GLOBAL
或
RTLD_DEFAULT
(默认值)的按位或。在 Windows 上,
mode
被忽略。
handle
指定已加载库的句柄(如果可用),默认值为
None
。
use_errno
是一个布尔标志,为加载库中 C
errno
变量的处理添加额外的安全层。此层在调用任何外部函数之前保存
errno
的线程本地副本,并在之后恢复该值,默认值为
False
。
use_last_error
是一个布尔标志,启用一对函数
get_last_error()
和
set_last_error()
,可用于操作系统错误代码,这些函数在 Windows 上更常用,默认值为
False
。
-
WinDLL(name [, mode [, handle [, use_errno [, use_last_error]]]])
:与
CDLL()
相同,不同之处在于库中的函数假定遵循 Windows stdcall 调用约定(仅适用于 Windows)。
以下实用函数可用于在系统上定位共享库,并构造适合用作上述类中
name
参数的名称。它定义在
ctypes.util
子模块中:
-
find_library(name)
:返回与库名称对应的路径名。
name
是没有任何文件后缀的库名称,如
'libc'
、
'libm'
等。该函数返回的字符串是完整的路径名,如
'/usr/lib/libc.so.6'
。此函数的行为高度依赖于系统,并且取决于共享库的底层配置和环境(例如,
LD_LIBRARY_PATH
的设置和其他参数)。如果找不到库,则返回
None
。
graph TD;
A[开始] --> B[加载共享库];
B --> C{选择库类型};
C -->|CDLL| D[标准 C 共享库];
C -->|WinDLL| E[Windows stdcall 调用约定库];
D --> F[使用库函数];
E --> F;
F --> G[结束];
5.2 外部函数
CDLL()
类创建的共享库实例作为底层 C 库的代理。要访问库内容,只需使用属性查找(
.
运算符)。例如:
>>> import ctypes
>>> libc = ctypes.CDLL("/usr/lib/libc.dylib")
>>> libc.rand()
16807
>>> libc.atoi("12345")
12345
>>>
在这个例子中,
libc.rand()
和
libc.atoi()
等操作直接调用加载的 C 库中的函数。
ctypes
假设所有函数接受
int
或
char *
类型的参数,并返回
int
类型的结果。因此,尽管前面的函数调用“有效”,但对其他 C 库函数的调用可能不会按预期工作。例如:
>>> libc.atof("34.5")
-1073746168
>>>
为了解决这个问题,可以通过更改以下属性来设置任何外部函数
func
的类型签名和处理方式:
-
func.argtypes
:描述
func
输入参数的
ctypes
数据类型元组。
-
func.restype
:描述
func
返回值的
ctypes
数据类型。对于返回
void
的函数,使用
None
。
-
func.errcheck
:一个接受三个参数(
result
、
func
、
args
)的 Python 可调用对象,其中
result
是外部函数返回的值,
func
是对外部函数本身的引用,
args
是输入参数的元组。此函数在外部函数调用后调用,可用于执行错误检查和其他操作。
以下是修复
atof()
函数接口的示例:
>>> libc.atof.restype=ctypes.c_double
>>> libc.atof("34.5")
34.5
>>>
ctypes.d_double
是对预定义数据类型的引用。下一节将介绍这些数据类型。
5.3 数据类型
表 1 显示了可用于外部函数的
argtypes
和
restype
属性的
ctypes
数据类型。“Python 值”列描述了给定数据类型接受的 Python 数据类型。
| ctypes 类型名称 | C 数据类型 | Python 值 |
|---|---|---|
c_bool
|
bool
|
True
或
False
|
c_bytes
|
signed char
| 小整数 |
c_char
|
char
| 单个字符 |
c_char_p
|
char *
| 以空字符结尾的字符串或字节 |
c_double
|
double
| 浮点数 |
c_longdouble
|
long double
| 浮点数 |
c_float
|
float
| 浮点数 |
c_int
|
int
| 整数 |
c_int8
|
signed char
| 8 位整数 |
c_int16
|
short
| 16 位整数 |
c_int32
|
int
| 32 位整数 |
c_int64
|
long long
| 64 位整数 |
c_long
|
long
| 整数 |
c_longlong
|
long long
| 整数 |
c_short
|
short
| 整数 |
c_size_t
|
size_t
| 整数 |
c_ubyte
|
unsigned char
| 无符号整数 |
c_uint
|
unsigned int
| 无符号整数 |
c_uint8
|
unsigned char
| 8 位无符号整数 |
c_uint16
|
unsigned short
| 16 位无符号整数 |
c_uint32
|
unsigned int
| 32 位无符号整数 |
c_uint64
|
unsigned long long
| 64 位无符号整数 |
c_ulong
|
unsigned long
| 无符号整数 |
c_ulonglong
|
unsigned long long
| 无符号整数 |
c_ushort
|
unsigned short
| 无符号整数 |
c_void_p
|
void *
| 整数 |
c_wchar
|
wchar_t
| 单个 Unicode 字符 |
c_wchar_p
|
wchar_t *
| 以空字符结尾的 Unicode 字符串 |
要创建表示 C 指针的类型,可以对其他类型应用以下函数:
-
POINTER(type)
:定义一个指向
type
类型的指针类型。例如,
POINTER(c_int)
表示 C 类型
int *
。
要定义表示固定大小 C 数组的类型,可以将现有类型乘以数组维度数。例如,
c_int*4
表示 C 数据类型
int[4]
。
要定义表示 C 结构或联合的类型,可以从基类
Structure
或
Union
继承。在每个派生类中,定义一个类变量
_fields_
来描述内容。
_fields_
是一个由 2 或 3 个元组组成的列表,形式为
(name, ctype)
或
(name, ctype, width)
,其中
name
是结构字段的标识符,
ctype
是描述类型的
ctype
类,
width
是整数位字段宽度。例如,考虑以下 C 结构:
struct Point {
double x, y;
};
该结构的
ctypes
描述如下:
class Point(Structure):
_fields_ = [ ("x", c_double),
("y", c_double) ]
5.4 调用外部函数
要调用库中的函数,只需使用与函数类型签名兼容的一组参数调用相应的函数。对于简单的数据类型,如
c_int
、
c_double
等,可以直接传递兼容的 Python 类型作为输入(整数、浮点数等)。也可以传递
c_int
、
c_double
等类型的实例作为输入。对于数组,可以直接传递兼容类型的 Python 序列。
要将指针传递给外部函数,必须首先创建一个表示将被指向的值的
ctypes
实例,然后使用以下函数之一创建指针对象:
-
byref(cvalue [, offset])
:表示指向
cvalue
的轻量级指针。
cvalue
必须是
ctypes
数据类型的实例。
offset
是要添加到指针值的字节偏移量。该函数返回的值只能在函数调用中使用。
-
pointer(cvalue)
:创建一个指向
cvalue
的指针实例。
cvalue
必须是
ctypes
数据类型的实例。这将创建前面描述的
POINTER
类型的实例。
以下示例展示了如何将
double *
类型的参数传递给 C 函数:
dval = c_double(0.0) # Create a double instance
r = foo(byref(dval)) # Calls foo(&dval)
p_dval = pointer(dval) # Creates a pointer variable
r = foo(p_dval) # Calls foo(p_dval)
# Inspect the value of dval afterwards
print (dval.value)
需要注意的是,不能创建指向内置类型(如
int
或
float
)的指针。如果底层 C 函数更改了值,传递指向此类类型的指针将违反可变性。
ctypes
实例
cobj
的
cobj.value
属性包含内部数据。例如,前面代码中对
dval.value
的引用返回存储在
ctypes c_double
实例
dval
中的浮点值。
要将结构传递给 C 函数,必须创建结构或联合的实例。为此,可以按如下方式调用先前定义的结构或联合类型
StructureType
:
StructureType(*args, **kwargs)
创建
StructureType
的实例,其中
StructureType
是从
Structure
或
Union
派生的类。
*args
中的位置参数用于按
_fields_
中列出的顺序初始化结构成员。
**kwargs
中的关键字参数仅初始化命名的结构成员。
5.5 替代类型构造方法
所有
ctypes
类型(如
c_int
、
POINTER
等)的实例都有一些类方法,用于从内存位置和其他对象创建
ctypes
类型的实例。
-
ty.from_buffer(source [,offset])
:创建一个与
source
共享相同内存缓冲区的
ctypes
类型
ty
的实例。
source
必须是支持可写缓冲区接口的任何其他对象(例如,
bytearray
、
array
模块中的数组对象、
mmap
等)。
offset
是从缓冲区开始使用的字节数。
-
ty.from_buffer_copy(source [, offset])
:与
ty.from_buffer()
相同,不同之处在于会复制内存,并且
source
可以是只读的。
-
ty.from_address(address)
:从指定为整数的原始内存地址
address
创建
ctypes
类型
ty
的实例。
-
ty.from_param(obj)
:从 Python 对象
obj
创建
ctypes
类型
ty
的实例。只有当传递的对象
obj
可以适应适当的类型时,此方法才有效。例如,Python 整数可以适应
c_int
实例。
-
ty.in_dll(library, name)
:从共享库中的符号创建
ctypes
类型
ty
的实例。
library
是加载的库的实例,如
CDLL
类创建的对象。
name
是符号的名称。此方法可用于在库中定义的全局变量周围放置
ctypes
包装器。
以下示例展示了如何创建对库
libexample.so
中定义的全局变量
int status
的引用:
libexample = ctypes.CDLL("libexample.so")
status = ctypes.c_int.in_dll(libexample,"status")
5.6 实用函数
ctypes
定义了以下实用函数:
-
addressof(cobj)
:返回
cobj
的内存地址作为整数。
cobj
必须是
ctypes
类型的实例。
-
alignment(ctype_or_obj)
:返回
ctypes
类型或对象的整数对齐要求。
ctype_or_obj
必须是
ctypes
类型或类型的实例。
-
cast(cobj, ctype)
:将
ctypes
对象
cobj
转换为
ctype
给定的新类型。此方法仅适用于指针,因此
cobj
必须是指针或数组,
ctype
必须是指针类型。
-
create_string_buffer(init [, size])
:创建一个可变字符缓冲区,作为
c_char
类型的
ctypes
数组。
init
可以是整数大小或表示初始内容的字符串。
size
是可选参数,如果
init
是字符串,则指定要使用的大小。默认情况下,大小设置为比
init
中的字符数大 1。Unicode 字符串使用默认编码编码为字节。
-
create_unicode_buffer(init [, size])
:与
create_string_buffer()
相同,不同之处在于创建的是
c_wchar
类型的
ctypes
数组。
-
get_errno()
:返回
ctypes
私有副本的
errno
的当前值。
-
get_last_error()
:返回 Windows 上
ctypes
私有副本的
LastError
的当前值。
-
memmove(dst, src, count)
:将
count
字节从
src
复制到
dst
。
src
和
dst
可以是表示内存地址的整数,也可以是可以转换为指针的
ctypes
类型的实例。与 C
memmove()
库函数相同。
-
memset(dst, c, count)
:将从
dst
开始的
count
字节内存设置为字节值
c
。
dst
可以是整数或
ctypes
实例。
c
是表示 0 - 255 范围内字节的整数。
-
resize(cobj, size)
:调整用于表示
ctypes
对象
cobj
的内部内存大小。
size
是新的字节大小。
-
set_conversion_mode(encoding, errors)
:设置从 Unicode 字符串转换为 8 位字符串时使用的 Unicode 编码。
encoding
是编码名称,如
'utf-8'
,
errors
是错误处理策略,如
'strict'
或
'ignore'
。返回包含先前设置的元组
(encoding, errors)
。
-
set_errno(value)
:设置
ctypes
私有副本的系统
errno
变量。返回先前的值。
-
set_last_error(value)
:设置 Windows
LastError
变量并返回先前的值。
-
sizeof(type_or_cobj)
:返回
ctypes
类型或对象的大小(以字节为单位)。
-
string_at(address [, size])
:返回表示从地址
address
开始的
size
字节内存的字节字符串。如果省略
size
,则假定字节字符串以空字符结尾。
-
wstring_at(address [, size])
:返回表示从地址
address
开始的
size
个宽字符的 Unicode 字符串。如果省略
size
,则假定字符字符串以空字符结尾。
5.7 示例
以下示例展示了如何使用
ctypes
模块构建一个接口,以访问本章第一部分中手动创建 Python 扩展模块时使用的一组 C 函数:
# example.py
import ctypes
_example = ctypes.CDLL("./libexample.so")
# int gcd(int, int)
gcd = _example.gcd
gcd.argtypes = (ctypes.c_int,
ctypes.c_int)
gcd.restype = ctypes.c_int
# int replace(char *s, char olcdh, char newch)
_example.replace.argtypes = (ctypes.c_char_p,
ctypes.c_char,
ctypes.c_char)
_example.replace.restype = ctypes.c_int
def replace(s, oldch, newch):
sbuffer = ctypes.create_string_buffer(s)
nrep = _example.replace(sbuffer,oldch,newch)
return (nrep,sbuffer.value)
# double distance(Point *p1, Point *p2)
class Point(ctypes.Structure):
_fields_ = [ ("x", ctypes.c_double),
("y", ctypes.c_double) ]
_example.distance.argtypes = (ctypes.POINTER(Point),
ctypes.POINTER(Point))
_example.distance.restype = ctypes.c_double
def distance(a,b):
p1 = Point(*a)
p2 = Point(*b)
return _example.distance(byref(p1),byref(p2))
通过以上内容,我们详细介绍了在 C 中嵌入 Python 解释器以及使用
ctypes
模块访问 C 代码的方法,包括宏的使用、基本操作函数、数据类型转换、外部函数调用等方面。这些技术可以帮助开发者在不同的场景中灵活运用 Python 和 C 的优势,实现更高效的编程。
扩展与嵌入 Python:从 C 语言到 Python 的无缝衔接
6. 综合应用与注意事项
在实际应用中,将 Python 嵌入 C 或者使用
ctypes
模块访问 C 代码,需要综合考虑多个方面,以确保程序的正确性和性能。
6.1 内存管理
在嵌入 Python 解释器的 C 代码中,正确的内存管理至关重要。如前面提到的,需要注意引用计数的管理。当从 Python 获得对象引用时,引用计数会增加;而当不再使用这些对象时,需要调用
Py_DECREF
或
Py_XDECREF
来减少引用计数,防止内存泄漏。例如在前面的
re
模块调用示例中:
Py_DECREF(args);
Py_DECREF(pat);
Py_DECREF(re_compile);
Py_DECREF(re);
这些操作确保了不再使用的对象所占用的内存能够被正确释放。
在使用
ctypes
时,虽然 Python 会自动管理大部分内存,但对于手动分配的内存,如使用
create_string_buffer
创建的缓冲区,需要确保在不再使用时进行适当的处理。
6.2 错误处理
无论是嵌入 Python 解释器还是使用
ctypes
调用 C 函数,都需要进行错误处理。在 Python 嵌入中,
PyRun_SimpleString
等函数会返回错误码,可以根据返回值判断操作是否成功。例如:
if (PyRun_SimpleString("print('Hello World')") == -1) {
// 处理错误
}
在
ctypes
中,可以通过设置
func.errcheck
属性来进行错误检查。例如:
def errcheck(result, func, args):
if result == -1:
raise OSError("Function call failed")
return result
libc.some_function.errcheck = errcheck
6.3 性能考虑
在性能方面,嵌入 Python 解释器会带来一定的开销,因为需要初始化和管理解释器。如果需要频繁调用 Python 代码,这种开销可能会影响性能。可以考虑将一些常用的 Python 代码提前编译或缓存,以减少重复执行的开销。
使用
ctypes
调用 C 函数时,参数传递和数据类型转换也会带来一定的性能损失。尽量使用简单的数据类型和直接的参数传递方式,避免不必要的转换。
7. 常见问题及解决方案
在实际使用过程中,可能会遇到一些常见问题,以下是一些解决方案:
7.1 找不到共享库
当使用
CDLL
或
WinDLL
加载共享库时,可能会遇到找不到库的问题。可以使用
find_library
函数来定位库的路径。例如:
import ctypes
from ctypes.util import find_library
lib_path = find_library('libc')
if lib_path:
libc = ctypes.CDLL(lib_path)
else:
print("Library not found")
7.2 函数调用结果异常
如前面提到的,
ctypes
默认假设函数接受
int
或
char *
类型的参数,并返回
int
类型的结果。如果调用结果异常,需要检查函数的类型签名,通过设置
argtypes
和
restype
属性来确保参数和返回值的类型正确。
7.3 内存泄漏
内存泄漏是一个常见的问题,特别是在嵌入 Python 解释器时。需要仔细检查引用计数的管理,确保所有创建的对象都被正确释放。可以使用内存分析工具来帮助检测和定位内存泄漏问题。
8. 总结
通过本文的介绍,我们了解了如何在 C 中嵌入 Python 解释器以及使用
ctypes
模块访问 C 代码。以下是对主要内容的总结:
-
嵌入 Python 解释器
:可以通过包含
Python.h头文件,使用一系列函数如Py_Initialize、PyRun_SimpleString等来初始化解释器、运行脚本,并在结束时使用Py_Finalize清理解释器。在编译和链接时,需要包含相应的头文件和库文件,并注意平台差异。 -
从 C 访问 Python
:可以使用 Python C API 中的函数,如
PyImport_ImportModule、PyObject_GetAttrString等,来导入模块、获取对象属性、调用函数等。同时要注意引用计数的管理。 -
Python 对象转换为 C
:可以使用一些低级转换函数,如
PyInt_AsLong、PyFloat_AsDouble等,将基本的 Python 数据类型转换为 C 表示形式。 -
ctypes模块 :可以使用CDLL或WinDLL加载共享库,通过设置函数的argtypes和restype属性来确保正确的参数和返回值类型。同时,ctypes提供了丰富的数据类型和实用函数,方便与 C 代码进行交互。
通过合理运用这些技术,可以充分发挥 Python 和 C 的优势,实现高效、灵活的编程。在实际应用中,需要根据具体需求和场景,综合考虑内存管理、错误处理和性能等方面的因素,以确保程序的正确性和稳定性。
流程图总结
graph LR;
A[开始] --> B{选择方式};
B -->|嵌入 Python 解释器| C[初始化解释器];
B -->|使用 ctypes| D[加载共享库];
C --> E[运行 Python 脚本];
E --> F[清理解释器];
D --> G[设置函数类型签名];
G --> H[调用 C 函数];
F --> I[结束];
H --> I;
操作步骤总结
嵌入 Python 解释器操作步骤
-
包含
Python.h头文件。 -
调用
Py_Initialize初始化解释器。 -
使用
PyRun_SimpleString或其他相关函数运行脚本。 - 处理脚本执行结果,进行必要的错误处理。
-
调用
Py_Finalize清理解释器。
使用
ctypes
访问 C 代码操作步骤
-
导入
ctypes模块。 -
使用
CDLL或WinDLL加载共享库。 -
设置函数的
argtypes和restype属性。 - 调用 C 函数,处理返回结果。
- 进行必要的错误处理和内存管理。
通过以上总结和操作步骤,希望能够帮助读者更好地理解和应用这些技术,在实际项目中实现 Python 和 C 的无缝衔接。
超级会员免费看

被折叠的 条评论
为什么被折叠?



