Python与C交互之指针,一篇文章搞懂内核编程

简介: Python与C交互之指针,一篇文章搞懂内核编程

1、指针类型


通过 POINTER(ctypes type)定义指针类型

T_int_ptr = POINTER(c_int)


等价于C的

typedef int* T_int_ptr


ctypes自带的指针类型有

ctypes类型 C类型 python类型
c_char_p char * (NUL terminated) bytes object or None
c_wchar_p wchar_t * (NUL terminated) string or None
c_void_p void * int or None


其它类型只能通过POINTER定义,包括我们的自定义类型(如结构体)

某些时候,ctypes可以在python类型与C类型间自动转换


(1)如果函数的参数定义为POINTER(type),那调用函数时可以直接输入type,会自动执行byref

libc.myfunc.argtypes = [POINTER(c_int)]   
i = c_int(32)
libc.myfunc(i)     #方式1
libc.myfunc(byref(i))  #方式2


方式1等价于方式2,跟C++的形参引用一样,使用时输入变量本身

void myfunc(int &i)
{
  i = 0;
}
void main()
{
  int i = 32;
  myfunc(i);
}


原文在Type conversions一节

In addition, if a function argument is explicitly declared to be a pointer type (such as POINTER(c_int)) in argtypes, 
an object of the pointed type (c_int in this case) can be passed to the function. ctypes will apply the required byref() 
conversion in this case automatically.


(2)几种python类型可以自动转换为C类型,包括None, integers, bytes objects and (unicode) strings,也就不需要事先转换为ctypes类型了


原文在Calling functions一节

None, integers, bytes objects and (unicode) strings are the only native Python objects that can directly be used as 
parameters in these function calls. None is passed as a C NULL pointer, bytes objects and strings are passed as 
pointer to the memory block that contains their data (char * or wchar_t *). Python integers are passed as the 
platforms default C int type, their value is masked to fit into the C type.

2 指针对象


通过pointer(object)取一个对象的指针

i = c_int(42)
pi = pointer(i)


pi称为一个指针对象(也是一个对象!),它本身的值并非所指向对象的内存地址,而C中指针变量的值就是所指向的内存地址

pi = pointer(i)   
pi    # <ctypes.wintypes.LP_c_long at 0x8b6bb48> 这是对象pi的地址,并非i的地址;


访问指针第n个元素

val = pi[0]   #通过下标读
pi[0] = c_int(0)    #通过下标写


下标支持负数,指针对象没有长度限制,所以千万注意不要越界访问!


关于contents属性

pi1 = pi.contents    #通过contents获得指针所指的内容


注意,contents返回的是一个新的对象,并非原对象本身

pi.contents is i   #返回False
pi.contents is pi.contents   #返回False


所以,向contents赋值也不会修改原对象的内容,而是将指针指向了新的对象

3、引用


通过byref取一个对象的引用。对象必须是ctypes类型

i = c_int(42)
ri = byref(i)


等价于C的

(char *)&obj


跟pointer一样,引用也是一个对象,拥有自己的地址

ri = byref(i)   
ri    # <cparam 'P' (0000000008B6BB10)> 这是对象ri的地址,并非i的地址

4、数组


ctypes的Array

The recommended way to create concrete array types is by multiplying any ctypes data type with a positive 
integer. Alternatively, you can subclass this type and define _length_ and _type_ class variables. Array elements 
can be read and written using standard subscript and slice accesses; for slice reads, the resulting object is not 
itself an Array.


定义一个数组的两种方式:


(1)定义数组类型

from ctypes import *
#数值数组
TenIntegers = c_int * 10    #TenIntegers 是一个类型,代表10个int的数组
iarr = TenIntegers(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
#字符串数组
T_char_arr = c_char * 12   # ctypes.c_char_Array_12
carr = T_char_arr(0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C,0x64, 0x00)  
ctypes.string_at(byref(ra))


(2)从列表构造

pyarray = [1,2,3,4,5,6,7,8,9,10]
carray = (ctypes.c_int*len(pyarray))(*pyarray)


本质上还是通过定义数组类型,以上可以分解为两步

arrtype = ctypes.c_int*len(pyarray)     #1
arr = arrtype (*pyarray )  #2


注意:其中的#1的星号代表乘号,而#2的星号代表从pyarray 逐个元素取出

数组的访问方式,下标或遍历

carray[0]      #下标读
carray[0]  = 10    #下标写
for i in ii: print(i, end=" ")    #遍历


C中数组名就是首地址指针,其实ctypes.Array也一样,传递数组对象就是传递指针,可以实现in-place操作

libc.myfunc.argtypes = [POINTER(c_int), c_int]   #C动态库函数,myfunc(int* arr, int len),修改传入数组的值
libc.myfunc(carray, 10)     #函数返回后,carray的值将被修改

5、空指针


通过定义类型得到空指针

null_ptr = POINTER(c_int)()
null_ptr   # <ctypes.wintypes.LP_c_long at 0x8b6bdc8>,空指针也是一个指针对象,也存在其地址
null_ptr[0]  # ValueError: NULL pointer access, 由于指向为空抛出异常,python会自行检测
null_ptr[0] = c_int(1)    # ValueError: NULL pointer access
null_ptr.contents    # ValueError: NULL pointer access
null_ptr.contents  = c_int(1)   # 这里可以正常运行,因为给contents属性赋值改变了指针的指向,指向了有意义的地址
null_ptr[0] = c_int(2)  # 上面的1被修改为2


另外,使用None会自动转换为空指针

6、申请内存


python自带垃圾回收,没有类似C++的new/delete。硬是找到有一个ctypes.create_string_buffer


该函数本意是用于bytes object的字符串的(当然还有unicode版本的create_unicode_buffer)

mstr = 'Hello world'
buf = ctypes.create_string_buffer(mstr.encode('ascii'))   # <ctypes.c_char_Array_12 at 0x8b6bc48>   长度为12的c_char数组
ctypes.string_at( byref(buf))    # b'Hello world'


也可以单纯用来作为一个缓冲区

mytype = c_int
pyarray = [1,2,3,4,5,6,7,8,9,10]
carray = (mytype*len(pyarray))(*pyarray)    #源数据
count = 10
bufsz = count*sizeof(mytype)
buf = ctypes.create_string_buffer(bufsz)   #创建缓冲区
ctypes.memmove(byref(buf), carray , bufsz)  #往缓冲区拷贝数据
res = ctypes.cast(buf, POINTER(mytype))   #转换为所需要的指针类型


注意到这里有一个函数ctypes.memmove,直接就是C的memcpy,类似的函数还有

ctypes.memset、ctypes.sizeof(希望官方能开放更多的C函数方便使用)

7、强制类型转换


这个函数显然是为了C专门准备的

ctypes.cast(obj, type)
This function is similar to the cast operator in C. It returns a new instance of type which points to the same memory block as obj.
 type must be a pointer type, and obj must be an object that can be interpreted as a pointer.


注意,只能用于指针对象的转换


有了cast,就可以用void * 来传递任意的类型指针

libc.myfunc.argtypes = [c_void_p, c_int]    #C动态库函数,myfunc(void* str, int len)   
buf = ctypes.create_string_buffer(256)   #字符串缓冲区
void_ptr = ctypes.cast(buf,c_void_p)
libc.myfunc(void_ptr,256)   #在myfunc内填充字符串缓冲区
char_ptr = ctypes.cast(void_ptr, POINTER(c_char))

8、函数指针


ctypes下给出了三种函数类型的定义方法

ctypes.CFUNCTYPE(restype, *argtypes, use_errno=False, use_last_error=False)
The returned function prototype creates functions that use the standard C calling convention. The function will release the GIL during the call. If use_errno is set to true, the ctypes private copy of the system errno variable is exchanged with the real errno value before and after the call; use_last_error does the same for the Windows error code.
ctypes.WINFUNCTYPE(restype, *argtypes, use_errno=False, use_last_error=False)
Windows only: The returned function prototype creates functions that use the stdcall calling convention, except on Windows CE where WINFUNCTYPE() is the same as CFUNCTYPE(). The function will release the GIL during the call. use_errno and use_last_error have the same meaning as above.
ctypes.PYFUNCTYPE(restype, *argtypes)
The returned function prototype creates functions that use the Python calling convention. The function will not release the GIL during the call.


第一个参数restype代表返回值,后面的依次为每个形参

调用约束:WINFUNCTYPE代表stdcall,CFUNCTYPE代表cdecl


主要用在定义C的回调函数

#python定义回调函数
def py_callback_func(data):   #通过回调函数返回一个浮点数
    print('callback : '+str(data))
    return
PyCallbackFunc = WINFUNCTYPE(None,c_float)      #定义函数类型
libc.funcWithCallback(PyCallbackFunc(py_callback_func))      #C库函数  void funcWithCallback(callback func)

10、numpy相关


[官方文档][Link 1]ctypes一节有一些说明


对于大数据、多维矩阵,不适合用ctypes.create_string_buffer的方式,此时可以用numpy的接口进行指针操作,两种方式


(1)numpy.ndarray.ctypes.data_as方法

import numpy as np
x = np.zeros((10,10),np.float32)   # 定义一个10*10的二维矩阵,类型为float
cptr =  x.ctypes.data_as(POINTER(ctypes.c_float))    #转为C类型指针
libc.myfunc(cptr, 10, 10)   #C库函数  void myfunc(float* matrix, int rows, int cols)


(2)numpy.ctypeslib.ndpointer方法

numpy.ctypeslib.ndpointer(dtype=None, ndim=None, shape=None, flags=None)[source]
Array-checking restype/argtypes.
An ndpointer instance is used to describe an ndarray in restypes and argtypes specifications. This approach is 
more flexible than using, for example, POINTER(c_double), since several restrictions can be specified, which are 
verified upon calling the ctypes function. These include data type, number of dimensions, shape and flags. If a 
given array does not satisfy the specified restrictions, a TypeError is raised.


这种方式的好处是ndpointer会检查输入数据是否匹配 指定的类型、维数、形状和标志(当然也可以不指定)

Parameters: 
dtype : data-type, optional
Array data-type.
ndim : int, optional
Number of array dimensions.
shape : tuple of ints, optional
Array shape.
flags : str or tuple of str
Array flags; may be one or more of:
C_CONTIGUOUS / C / CONTIGUOUS
F_CONTIGUOUS / F / FORTRAN
OWNDATA / O
WRITEABLE / W
ALIGNED / A
WRITEBACKIFCOPY / X
UPDATEIFCOPY / U


例子

import numpy as np
x = np.zeros((10,10),np.float32)   # 定义一个10*10的二维矩阵,类型为float
libc.myfunc.argtypes = [ndpointer(ctypes.c_float), ctypes.c_int,  ctypes.c_int]
libc.myfunc.restype = None
libc.myfunc(x, 10, 10)   #C库函数  void myfunc(float* matrix, int rows, int cols)

11、效率

考虑开销

create_string_buffer的开销

(待续)


相关文章
|
29天前
|
人工智能 数据可视化 数据挖掘
探索Python编程:从基础到高级
在这篇文章中,我们将一起深入探索Python编程的世界。无论你是初学者还是有经验的程序员,都可以从中获得新的知识和技能。我们将从Python的基础语法开始,然后逐步过渡到更复杂的主题,如面向对象编程、异常处理和模块使用。最后,我们将通过一些实际的代码示例,来展示如何应用这些知识解决实际问题。让我们一起开启Python编程的旅程吧!
|
28天前
|
存储 数据采集 人工智能
Python编程入门:从零基础到实战应用
本文是一篇面向初学者的Python编程教程,旨在帮助读者从零开始学习Python编程语言。文章首先介绍了Python的基本概念和特点,然后通过一个简单的例子展示了如何编写Python代码。接下来,文章详细介绍了Python的数据类型、变量、运算符、控制结构、函数等基本语法知识。最后,文章通过一个实战项目——制作一个简单的计算器程序,帮助读者巩固所学知识并提高编程技能。
|
16天前
|
Unix Linux 程序员
[oeasy]python053_学编程为什么从hello_world_开始
视频介绍了“Hello World”程序的由来及其在编程中的重要性。从贝尔实验室诞生的Unix系统和C语言说起,讲述了“Hello World”作为经典示例的起源和流传过程。文章还探讨了C语言对其他编程语言的影响,以及它在系统编程中的地位。最后总结了“Hello World”、print、小括号和双引号等编程概念的来源。
102 80
|
2月前
|
存储 索引 Python
Python编程数据结构的深入理解
深入理解 Python 中的数据结构是提高编程能力的重要途径。通过合理选择和使用数据结构,可以提高程序的效率和质量
150 59
|
5天前
|
Python
[oeasy]python055_python编程_容易出现的问题_函数名的重新赋值_print_int
本文介绍了Python编程中容易出现的问题,特别是函数名、类名和模块名的重新赋值。通过具体示例展示了将内建函数(如`print`、`int`、`max`)或模块名(如`os`)重新赋值为其他类型后,会导致原有功能失效。例如,将`print`赋值为整数后,无法再用其输出内容;将`int`赋值为整数后,无法再进行类型转换。重新赋值后,这些名称失去了原有的功能,可能导致程序错误。总结指出,已有的函数名、类名和模块名不适合覆盖赋新值,否则会失去原有功能。如果需要使用类似的变量名,建议采用其他命名方式以避免冲突。
27 14
|
15天前
|
分布式计算 大数据 数据处理
技术评测:MaxCompute MaxFrame——阿里云自研分布式计算框架的Python编程接口
随着大数据和人工智能技术的发展,数据处理的需求日益增长。阿里云推出的MaxCompute MaxFrame(简称“MaxFrame”)是一个专为Python开发者设计的分布式计算框架,它不仅支持Python编程接口,还能直接利用MaxCompute的云原生大数据计算资源和服务。本文将通过一系列最佳实践测评,探讨MaxFrame在分布式Pandas处理以及大语言模型数据处理场景中的表现,并分析其在实际工作中的应用潜力。
51 2
|
28天前
|
小程序 开发者 Python
探索Python编程:从基础到实战
本文将引导你走进Python编程的世界,从基础语法开始,逐步深入到实战项目。我们将一起探讨如何在编程中发挥创意,解决问题,并分享一些实用的技巧和心得。无论你是编程新手还是有一定经验的开发者,这篇文章都将为你提供有价值的参考。让我们一起开启Python编程的探索之旅吧!
46 10
|
1月前
|
机器学习/深度学习 人工智能 Java
Python 语言:强大、灵活与高效的编程之选
本文全面介绍了 Python 编程语言,涵盖其历史、特点、应用领域及核心概念。从 1989 年由 Guido van Rossum 创立至今,Python 凭借简洁的语法和强大的功能,成为数据科学、AI、Web 开发等领域的首选语言。文章还详细探讨了 Python 的语法基础、数据结构、面向对象编程等内容,旨在帮助读者深入了解并有效利用 Python 进行编程。
|
30天前
|
机器学习/深度学习 人工智能 数据挖掘
探索Python编程的奥秘
在数字世界的海洋中,Python如同一艘灵活的帆船,引领着无数探险者穿梭于数据的波涛之中。本文将带你领略Python编程的魅力,从基础语法到实际应用,一步步揭开Python的神秘面纱。
44 12
|
29天前
|
IDE 程序员 开发工具
Python编程入门:打造你的第一个程序
迈出编程的第一步,就像在未知的海洋中航行。本文是你启航的指南针,带你了解Python这门语言的魅力所在,并手把手教你构建第一个属于自己的程序。从安装环境到编写代码,我们将一步步走过这段旅程。准备好了吗?让我们开始吧!