python调用ctypes接收指针,ctypes教程

  python调用ctypes接收指针,ctypes教程

  最近在看Python性能优化的文章时,突然想起了ctypes这个模块,不太懂。但是,重新看了一遍相关资料,我有了一些新的看法。

  Ctypes是提供Python类型和C类型数据之间接口的模块,或者说是规则的模块。ctypes定义的数据类型其实不是数据类型,而是一种转换规则。ctypes定义的所有数据类型都需要关联Python数据类型,然后传递给C函数进行调用。C函数调用时,按照ctypes的数据类型进行转换,关联的Python数据类型转换成C数据类型,传递给C函数。如果是ctypes定义的指针或地址,实际上是将Python变量对应的内存地址中的数据与ctypes的数据类型关联起来。如果在C函数内部修改了传入的指针地址对应的变量,最后,ctypes将修改后的C数据类型转换为Python数据,并存储在之前Python变量对应的内存空间中。

  在调用ctypes时,程序的内存空间实际上可以分为Python数据内存空间和C数据类型空间。ctypes定义的数据类型提供了Python数据类型和C数据类型转换之间的对应关系。ctypes定义的数据类型需要与Python数据类型相关联,在调用C函数时实时转换为C数据类型。其中Python数据类型存在于Python数据内存空间,C数据类型存在于C数据内存空间。

  需要注意的是,一般来说,C数据内存空间是实时打开的,用完了就自动销毁。当然也有特例,比如numpy定义的数组类型变量。numpy定义的数据类型实际上是打包的C数据类型。当然,numpy定义的数组类型变量存在于C数据内存空间,而numpy下的数组可以持久化,不会自动销毁。

  Ctypes提供了一些基本的数据类型来映射C语言和Python的类型。可以说ctypes是一种数据类型映射关系或者转换关系。

  给出ctypes的一些使用代码:

  从ctypes导入*

  从平台导入*

  cdll_names={

  达尔文: libc.dylib ,

  Linux : libc.so.6 ,

  Windows: msvcrt.dll

  }

  clib=cdll。LoadLibrary(cdll _ names[system()])

  a=ba

  b=bb

  s1=c_char_p(a)

  s2=c_char_p(b)

  打印(id(a),id(b))

  打印(-*80)

  print( s1.value,type(s1),id(s1),id(s1.value),type(s1.value))

  print( s2.value,type(s2),id(s2),id(s2.value),type(s2.value))

  打印( **80)

  clib.strcat.argtype=(c_char_p,c_char_p)

  clib.strcat.restype=c_char_p

  s3=clib.strcat(s1,s2)

  print( s1.value,type(s1),id(s1),id(s1.value),type(s1.value) ) #ab

  print( s2.value,type(s2),id(s2),id(s2.value),type(s2.value) ) #b

  #print(s3,类型(s3),id(s3))

  #print(a,id(a))

  #print(b,id(b))

  print(^*80)

  s1.value=bc

  print( s1.value,type(s1),id(s1),id(s1.value),type(s1.value) ) #ab

  print( s2.value,type(s2),id(s2),id(s2.value),type(s2.value) ) #b

  结果:

  (140474265252848, 140474265252896)

  -

  ( a ,class ctypes.c_char_p ,140474264436032,140474265252848,type str )

  ( b ,class ctypes.c_char_p ,140474263869200,140474265252896,type str )

  ********************************************************************************

  ( ab ,class ctypes.c_char_p ,140474264436032,140474263886368,type str )

  ( b ,class ctypes.c_char_p ,140474263869200,140474265252896,type str )

  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

  ( c ,class ctypes.c_char_p ,140474264436032,140474265292896,type str )

  ( b ,class ctypes.c_char_p ,140474263869200,140474265252896,type str )

  用ctypes定义结构转换规则:(转换成C数据内存空间时是链表结构)

  从ctypes导入*

  类别测试(结构):

  及格

  测试。_fields_=[(x ,c_int),

  ( y ,c_char),

  (下一个,指针(测试))]

  test=Test(11,97,无)

  test2=Test(12,98,指针(测试))

  test3=Test(13,99,指针(test2))

  打印(测试)

  打印(类型(测试))

  打印(test.x,test.y,test.next)

  print(type(test.x),type(test.y),type(test.next))

  test2=test3.next.contents

  打印(测试2)

  打印(类型(测试2))

  打印(测试2.x,测试2.y,测试2.next)

  print(type(test2.x),type(test2.y),type(test2.next))

  打印(测试3)

  打印(类型(测试3))

  打印(测试3.x,测试3.y,测试3.next)

  print (type (test3.x),type (test3.y),type (test3.next))测试类相当于封装了多个python数据类型和C数据类型之间的映射关系,是Structure类的继承类。而测试类的对象与对应的python数据类型对象相关,在转换时调用测试类的特定对象(例如test1,test2,test3)会根据封装的python数据类型在C数据内存空间中生成对应的映射C类型数据。

  需要注意的是,无论是数据类型还是结构都是由ctypes定义的,实际上并不需要为对应的C数据类型开放内存空间。ctypes只提供了它们之间的对应关系:

  比如:

  a=100

  b=ctypes.c_int(a)

  其中,B是表示基于Python数据类型int的A转换为C内存空间int的关系。当C函数调用B时,C语言int类型为100的数据按照B定义的规则自动在C数据内存空间打开。

  给出Python使用ctypes的官方说明:

  https://docs.python.org/zh-cn/3/library/ctypes.html

  为了说明Python变量和Python数据的内存空间之间的关系:

  Python变量a:

  变量a-对应于内存地址空间(13521458792) -存储在这个空间中的Python数据是Python type 999

  上述关系由定义a=999生成,其中id(a)=13521458792。

  ========================================================

  为了更清楚地证明ctypes只提供映射关系,可以看看下面的代码:

  导入类型

  随机导入

  d=ctypes.c_double*100000000

  数据=[]

  对于范围内的I(100000000):

  data.append(random.random())

  #上半部分

  #################################

  #下半部分

  C_data=d(*data)在ide中执行上述代码(一步一步):

  执行前面的代码后,检查内存使用情况:

  执行以下代码后,再次检查内存使用情况:

  可以看出,ctypes并没有提供数据类型转换,而是提供了数据转换时的对应映射关系。

  如果cytpes定义的数据类型会在C类内存空间中直接生成对应的C类数据,那么执行下一部分代码后的内存占用应该是24.2%以上,但实际上只是从12.1%增加到14.5%,多占用了2.4%的内存,而这2.4%的内存不可能是转换后的C类数据而是其对应的映射关系。

  ===============================================================================

  参考文章:

  ctypes中的指针:

  ==================================================================

  本文前面说过,Python程序中C语言的数据空间不会永远存在,而是用完了就会自动销毁。其实这种说法并不准确。如果我们将C语言的内存空间中的数据以一定的方式保存(有些数据结构存储在堆或栈中),也可以连续保存。当然,这种节省只是在C语言的内存空间中,如果要切换回Python类型空间,还是需要再次转换。

  给出一个C语言代码:

  //test.c

  #包含stdio.h

  #包含stdlib.h

  //点结构

  结构点

  {

  int x;

  char y;

  结构点* p;

  };

  静态结构点* head=NULL

  静态结构点* current=NULL

  静态结构点* walk=NULL

  void new_point(结构点*p)

  {

  如果(p)

  {

  If(current==NULL) //是第一个元素

  {

  head=p;

  电流=p;

  printf(第一个添加!\ n’);

  }

  Else //不是第一个元素

  {

  当前-

  电流=p;

  printf(not first add!\ n’);

  }

  //

  }

  }

  void print_point()

  {

  走路=头;

  if(walk==NULL)

  {

  printf(错误,是空列表!\ n’);

  }

  其他

  {

  而(走!=当前)

  {

  printf(x: %d,y: %c \n ,walk- x,walk-

  散步=散步-

  }

  如果(走!=空)

  {

  printf(x: %d,y: %c \n ,walk- x,walk-

  }

  }

  }

  void main()

  {

  结构点a;

  a.x=97

  阿y= a

  a.p=空

  结构点b;

  b.x=98

  乙y=乙

  b.p=空

  结构点c;

  c.x=99

  c.y= c

  c.p=空

  结构点d;

  d.x=100

  迪伊=德

  d.p=空

  结构点e;

  e.x=101

  伊伊=伊

  e.p=空

  新点(

  新点(

  新点(

  新点(

  新点(

  print _ point();

  }

  该文件命名为测试c。

  在Ubuntu18.04系统中编译并执行:

  (同groundcontrolcenter)地面控制中心测试

  编译成动态链接库:

  gcc-fPIC-共享测试。只编译并生成目标文件测试。因此

  有了动态链接库,我们就可以使用指针模块将计算机编程语言数据转为C类型数据并调用C语言下链接库的函数,给出代码:

  导入类型

  从指针导入c_int,c_char,结构,指针,指针,cdll

  类点(结构):

  及格

  点. fields_=[(x ,c_int),

  ( y ,c_char),

  (下一个,指针(点))]

  a=点(97,ba ,无)

  b=点(98,bb ,无)

  c=点(99,bc ,无)

  d=点(100,bd ,无)

  e=点(101,be ,无)

  a .下一步=指针(二)

  b .下一个=指针(三)

  c .下一步=指针(四)

  d.next=指针(五)

  clib=cdll .LoadLibrary(./测试。所以’)

  克里布。新观点。arg type=指针(Point)

  clib.new_point.restype=None

  clib.print_point.argtype=None

  clib.print_point.restype=None

  clib.new_point(指针(一))

  clib.new_point(指针(b))

  clib.new_point(指针(c))

  clib.new_point(指针(d))

  clib.new_point(指针(e))

  打印(-*50)

  clib.print_point()

  打印(-*50)

  clib.print_point()该计算机编程语言代码命名为测试。巴拉圭文件。

  运行结果:

  可以看到我们用指针定义好映射规则,即:点类的对象:a、b、c、d、e

  甲、乙、丙、丁、戊对象关联好了计算机编程语言命名空间下的计算机编程语言数据类型,当调用c语言库函数时将按照甲、乙、丙、丁、戊对象所定义的映射在C语言内存空间下生成对应的C语言数据类型。

  两次调用C库中的clib.print_point()函数均可以打印C内存空间下的栈中的数据,这充分说明本文前述内容的不充分的地方C语言内存空间下的数据只要我们加载的动态链接库的接口变量,这里是clib,还存在,就是可以一直调用的。

  如果我们申请完C语言内存空间后如果删除矢量字库目录会不会自动释放内存呢?

  代码:

  导入类型

  从指针导入c_int,c_char,结构,指针,指针、cdll、byref

  类点(结构):

  及格

  点. fields_=[(x ,c_int),

  ( y ,c_char),

  (下一个,指针(点))]

  clib=cdll .LoadLibrary(./测试。所以’)

  克里布。新观点。arg type=指针(Point)

  clib.new_point.restype=None

  clib.print_point.argtype=None

  clib.print_point.restype=None

  l=[]

  对于范围内的我(10000 * 10000):

  l.append( Point(i,ba ,None))

  clib.new_point(byref(l[-1])其中,test.so链接库为本文前面所给。

  在集成驱动电子设备执行:

  内存占用:

  删除矢量字库目录后查看内存情况:

  可以发现前面说的又有不对的地方,如果删除矢量字库目录变量了,但是C语言内存空间还是没有释放,看来最终的答案是C语言内存空间如果申请了就需要设置相应的C函数进行释放,如果没有进行C函数释放那么在计算机编程语言程序的生命周期内C语言内存空间所申请的空间都是会一直存在的。

  于是再次改测试。c的代码增加自由点函数:

  void新点(结构点*p)

  {

  如果(p)

  {

  if(current==NULL) //是第一个元素

  {

  head=p;

  电流=p;

  printf(第一个添加!\ n’);

  }

  else //不是第一个元素

  {

  当前-

  电流=p;

  printf(not first add!\ n’);

  }

  //

  }

  }

  完整的测试。c代码:

  //test.c

  #包含标准视频

  #包含标准库

  //点结构体

  结构点

  {

  int x;

  char y;

  结构点* p;

  };

  静态结构点* head=NULL

  静态结构点*当前=空

  静态结构点* walk=NULL

  void新点(结构点*p)

  {

  如果(p)

  {

  if(current==NULL) //是第一个元素

  {

  head=p;

  电流=p;

  printf(第一个添加!\ n’);

  }

  else //不是第一个元素

  {

  当前-

  电流=p;

  printf(not first add!\ n’);

  }

  //

  }

  }

  void print_point()

  {

  走路=头;

  if(walk==NULL)

  {

  printf(错误,是空列表!\ n’);

  }

  其他

  {

  而(走!=当前)

  {

  printf(x: %d,y: %c \n ,walk- x,walk-

  散步=散步-

  }

  如果(走!=空)

  {

  printf(x: %d,y: %c \n ,walk- x,walk-

  }

  }

  }

  void free_point()

  {

  而(头!=空)

  {

  走路=头;

  头=头-

  printf(begin删除一个元素!\ n’);

  printf(x: %d,y: %c \n ,walk- x,walk-

  免费(走);

  printf(成功删除一个元素!\ n’);

  //printf( %x \n ,walk);

  }

  }

  void main()

  {

  结构点a;

  a.x=97

  a.y= a

  a.p=NULL

  结构点b;

  b.x=98

  b.y= b

  b.p=NULL

  结构点c;

  c.x=99

  c.y= c

  c.p=NULL

  结构点d;

  d.x=100

  d.y= d

  d.p=NULL

  结构点e;

  e.x=101

  e.y= e

  e.p=NULL

  新点(

  新点(

  新点(

  新点(

  新点(

  print _ point();

  }查看代码

  完整的test.py代码:

  导入类型

  从ctypes导入c_int,c_char,结构,指针,指针,cdll,byref

  类点(结构):

  及格

  点。_fields_=[(x ,c_int),

  ( y ,c_char),

  (下一个,指针(点))]

  clib=cdll。LoadLibrary(。/test . so’)

  clib . new _ Point . arg type=POINTER(Point)

  clib.new_point.restype=None

  clib.print_point.argtype=None

  clib.print_point.restype=None

  clib.free_point.argtype=None

  clib.free_point.restype=None

  l=[]

  对于范围内的I(10000):

  l.append( Point(i,ba ,None))

  clib.new_point(byref(l[-1])

  打印(-*50)

  #clib.print_point()

  clib.free_point()查看代码

  这个函数释放内存的逻辑非常清楚,但是运行时会出错。

  错误提示也很明确,就是不能用free释放这个内存。查了半天C语言的语法,发现这种方式没有语法问题。虽然C语言已经学了10多年了,但这么简单的逻辑应该不会出错,这也很让人费解。

  最后在网上看到有人总结了C语言释放堆内存的解释,很有用,就是——“谁申请谁释放”

  在前面的操作中,我们删除了clib变量,所以不能再使用C语言动态链接库文件中定义的函数来操作数据,但此时不会释放C内存空间中的内存。如果我们删除与C内存空间关联的ctypes变量,也就是说我们用ctypes变量映射Python变量以隐式的方式在C语言内存空间生成相应的变量(在C内存空间生成的数据通过调用动态链接库中的函数自动映射)。然后,我们删除这个映射关系。Python中的ctypes会有自己的垃圾回收功能,在Python的垃圾回收机制下自动回收C内存空间产生的堆空间吗?

  所以操作:

  最后发现,虽然在ctypes中设置数据类型只是定义了一个映射关系,但是并不能在C内存空间中生成对应的变量。最后需要调用C内存空间中的函数来生成相应的C数据变量。但是,由于C内存空间与Python内存空间是隔离的,我们无法直接操作C内存空间中的数据。并且C内存中的数据本身遵守“谁生成谁销毁”的原则,导致我们无法使用C语言中的free函数释放相应的变量空间(这些变量空间是在ctypes下定义的数据类型调用C动态库中的函数时,Python的ctypes自动生成的)。所以我们只使用Python语言机制和ctypes语言机制来释放C内存空间中的变量,所以我们删除了对应的ctypes数据变量,也就是删除了Python变量和C变量之间的关联,从而自然触发Python语言中的垃圾收集机制来释放内存。最后的结论是,C语言的内存应用是谁申请,谁负责释放。

  转载请联系作者授权,否则将追究法律责任。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: