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的开销

(待续)


相关文章
|
3天前
|
存储 索引 Python
元组(Tuple)在Python编程中的应用与实例
元组(Tuple)在Python编程中的应用与实例
12 2
|
3天前
|
机器学习/深度学习 数据可视化 数据挖掘
Python编程的深入探索与实用案例
Python编程的深入探索与实用案例
12 3
|
13天前
|
存储 算法 安全
Python编程实验六:面向对象应用
Python编程实验六:面向对象应用
40 1
|
13天前
|
Python
Python编程作业五:面向对象编程
Python编程作业五:面向对象编程
43 1
|
3天前
|
C语言
文件类型指针及其在编程中的应用
文件类型指针及其在编程中的应用
9 0
|
3天前
|
C语言 Python
专为编程小白设计的Python零基础入门教程,GitHub星标破W
市面上大多数技术类的书籍都着重于一步步的构建系统的知识体系,并不是说这样就是不对的,但这样按部就班的学习注定了需要花费大量的时间用来掌握“基础知识”,或死记硬背,或慢慢理解。 然而世界不会迁就你,而是在步步紧逼的告诉你要赶紧学完,赶紧找工作,赶紧挣钱,这才是你生活的基础。 今天给小伙伴们带来了一份《编程小白的第一步Python书》,这本书是专为零基础小白设计的,不会告诉“先学C语言,会更好理解Python”这种狗屁道理。而是先带你掌握搭建项目所用到的最少得知识,再真实的项目搭建中实践自己的所学,逐渐的完善知识体系。
|
4天前
|
Python 容器
Python GUI编程(Tkinter)
Python GUI编程(Tkinter)
13 1
|
4天前
|
移动开发 开发框架 安全
Python CGI编程
Python CGI编程
|
5天前
|
存储 编译器 C语言
数组指针,高效编程之道
数组指针,高效编程之道
|
5天前
|
数据采集 运维 API
适合所有编程初学者,豆瓣评分8.6的Python入门手册开放下载!
Python是一种跨平台的计算机程序设计语言,它可以用来完成Web开发、数据科学、网络爬虫、自动化运维、嵌入式应用开发、游戏开发和桌面应用开发。 Python上手很容易,基本有其他语言编程经验的人可以在1周内学会Python最基本的内容(PS:没有基础的人也可以直接学习,速度会慢一点)