ctypes给扩展模块中的函数传递数组和结构体

传递数组

楔子

下面我们来看看如何使用ctypes传递数组,这里我们只讲传递,不讲返回。因为C语言返回数组给python实际上会存在很多问题,比如:返回的数组的内存由谁来管理,不用了之后空间由谁来释放,事实上ctypes内部对于返回数组支持的也不是很好。因此我们一般不会向python返回一个C语言中的数组,因为C语言中的数组传递给python涉及到效率的问题,python中的列表传递直接传递一个引用即可,但是C语言中的数组过来肯定是要拷贝一份的,所以这里我们只讲python如何通过ctypes给扩展模块传递数组,不会介绍扩展模块如何返回数组给python。

如何传递

我们知道python中没有数组,或者说C中的数组在python中是一个list,我们可以通过list来得到数组,方式也很简单。

from ctypes import *

# 创建一个数组,假设叫[1, 2, 3, 4, 5]
a5 = (c_int * 5)(1, 2, 3, 4, 5)
print(a5)  # <__main__.c_long_Array_5 object at 0x00000162428968C0>
# 上面这种方式就得到了一个数组
# 当然还可以使用list
a5 = (c_int * 5)(*range(1, 6))
print(a5)  # <__main__.c_long_Array_5 object at 0x0000016242896940>

下面演示一下。

//字符数组默认是以\0作为结束的,我们可以通过strlen来计算长度。
//但是对于整型的数组来说我们不知道有多长
//因此有两种声明参数的方式,一种是int a[n],指定数组的长度
//另一种是通过指定int *a的同时,再指定一个参数int size,调用函数的时候告诉函数这个数组有多长
int test1(int a[5])
{
    //可能有人会问了,难道不能通过sizeof计算吗?答案是不能,无论是int *a还是int a[n]
    //当它作为函数的参数,我们调用的时候,传递的都是指针,指针在64位机器上默认占8个字节。
    //所以int a[] = {...}这种形式,如果直接在当前函数中计算的话,那么sizeof(a)就是数组里面所有元素的总大小,因为a是一个数组名
    //但是当把a传递给一个函数的时候,那么等价于将a的首地址拷贝一份传过去,此时在新的函数中再计算sizeof(a)的时候就是一个指针的大小
    //至于int *a这种声明方式,不管在什么地方,sizeof(a)则都是一个指针的大小
    int i;
    int sum = 0;
    a[3] = 10;
    a[4] = 20;
    for (i = 0;i < 5; i++){
        sum += a[i];
    }
    return sum;
}
from ctypes import *

lib = CDLL("./mmp.dll")

# 创建5个元素的数组,但是只给3个元素
arr = (c_int * 5)(1, 2, 3)
# 在扩展模块中,设置剩余两个元素
# 所以如果没问题的话,结果应该是1 + 2 + 3 + 10 + 20
print(lib.test1(arr))  # 36

传递结构体

定义一个结构体

有了前面的数据结构还不够,我们还要看看结构体是如何传递的,有了结构体的传递,我们就能发挥更强大的功能。那么我们来看看如何使用ctypes定义一个结构体:

from ctypes import *

# 对于这样一个结构体应该如何定义呢?
"""
struct Girl {
  //姓名
  char *name;
  //年龄
  int age;
  //性别
  char *gender;
  //班级
  int class;
};
"""

# 定义一个类,必须继承自ctypes.Structure
class Girl(Structure):
    # 创建一个_fields_变量,必须是这个名字,注意开始和结尾都只有一个下划线
    # 然后就可以写结构体的字段了,具体怎么写估计一看就清晰了
    _fields_ = [
        ("name", c_char_p),
        ("age", c_int),
        ("gender", c_char_p),
        ("class", c_int)
    ]

如何传递

我们向C中传递一个结构体,然后再返回:

struct Girl {
  char *name;
  int age;
  char *gender;
  int class;
};

//接收一个结构体,返回一个结构体
struct Girl test1(struct Girl g){
  g.name = "古明地觉";
  g.age = 17;
  g.gender = "female";
  g.class = 2;
  return g;
}
from ctypes import *

lib = CDLL("./mmp.dll")

class Girl(Structure):
    _fields_ = [
        ("name", c_char_p),
        ("age", c_int),
        ("gender", c_char_p),
        ("class", c_int)
    ]

# 此时返回值类型就是一个Girl类型,另外我们这里的类型和C中结构体的名字不一样也是可以的
lib.test1.restype = Girl
# 传入一个实例,拿到返回值
g = Girl()
res = lib.test1(g)
print(res, type(res))  # <__main__.Girl object at 0x0000015423A06840> <class '__main__.Girl'>
print(res.name, str(res.name, encoding="utf-8"))  # b'\xe5\x8f\xa4\xe6\x98\x8e\xe5\x9c\xb0\xe8\xa7\x89' 古明地觉
print(res.age)  # 17
print(res.gender)  # b'female'
print(getattr(res, "class"))  # 2

如果是结构体指针呢?

struct Girl {
  char *name;
  int age;
  char *gender;
  int class;
};

//接收一个指针,返回一个指针
struct Girl *test1(struct Girl *g){
  g -> name = "mashiro";
  g -> age = 17;
  g -> gender = "female";
  g -> class = 2;
  return g;
}
from ctypes import *

lib = CDLL("./mmp.dll")

class Girl(Structure):
    _fields_ = [
        ("name", c_char_p),
        ("age", c_int),
        ("gender", c_char_p),
        ("class", c_int)
    ]

# 此时指定为Girl类型的指针
lib.test1.restype = POINTER(Girl)
# 传入一个实例,拿到返回值
# 如果lib.test1.restype指定的类型不是结构体指针,那么函数返回的就是该结构体(Girl)实例
# 但返回的是指针,我们还需要手动调用一个contents才可以拿到对应的值。
g = Girl()
res = lib.test1(byref(g))
print(str(res.contents.name, encoding="utf-8"))  # mashiro
print(res.contents.age)  # 16
print(res.contents.gender)  # b'female'
print(getattr(res.contents, "class"))  # 3

# 另外我们不仅可以通过返回的res去调用,因为我们传递的是g的指针
# 修改指针指向的内存就相当于修改g
# 所以我们通过g来调用也是可以的
print(str(g.name, encoding="utf-8"))  # mashiro

因此对于结构体来说,我们先创建一个结构体(Girl)实例g,如果扩展模块的函数中接收的是结构体,那么直接把g传进去等价于将g拷贝了一份,此时函数中进行任何修改都不会影响原来的g。但如果函数中接收的是结构体指针,我们传入byref(g)相当于把g的指针拷贝了一份,在函数中修改是会影响g的。而返回的res也是一个指针,所以我们除了通过res.contents来获取结构体中的值之外,还可以通过g来获取。再举个栗子对比一下:

struct Num {
  int x;
  int y;
};

struct Num test1(struct Num n){
  n.x += 1;
  n.y += 1;
  return n;
}

struct Num *test2(struct Num *n){
  n->x += 1;
  n->y += 1;
  return n;
}
from ctypes import *

lib = CDLL("./mmp.dll")

class Num(Structure):
    _fields_ = [
        ("x", c_int),
        ("y", c_int),
    ]

# 我们在创建的时候是可以传递参数的
num = Num(x=1, y=2)
print(num.x, num.y)  # 1 2

lib.test1.restype = Num
res = lib.test1(num)
# 我们看到通过res得到的结果是修改之后的值
# 但是对于num来说没有变
print(res.x, res.y)  # 2 3
print(num.x, num.y)  # 1 2
"""
因为我们将num传进去之后,相当于将num拷贝了一份。
函数里面的结构体和这里的num尽管长得一样,但是没有任何关系,自增1之后返回交给res。
所以res获取的结果是自增之后的结果,但是num还是之前的num
"""

# 我们来试试传递指针,将byref(num)再传进去
lib.test2.restype = POINTER(Num)
res = lib.test2(byref(num))
print(num.x, num.y)  # 2 3
"""
我们看到将指针传进去之后,相当于把num的指针拷贝了一份。
然后在函数中修改,相当于修改指针指向的内存,所以是会影响外面的num的
而扩展模块的函数中返回的是参数中的结构体指针,而我们传递的byref(num)也是这里的num的指针
尽管传递指针的时候也是拷贝了一份,两个指针本身来说虽然也没有任何联系,但是它们存储的地址是一样的
那么通过res.contents获取到的内容就相当于是这里的num
因此此时我们通过res.contents获取和通过num来获取都是一样的。
"""
print(res.contents.x, res.contents.y)  # 2 3

# 另外还需要注意的一点就是:如果传递的是指针,一定要先创建一个变量
# 比如这里,一定是:先要num = Num(),然后再byref(num)。不可以直接就byref(Num())
# 原因很简单,因为Num()这种形式在创建完Num实例之后就销毁了,因为没有变量保存它,那么此时再修改指针指向的内存就会有问题,因为内存的值已经被回收了
# 如果不是指针,那么可以直接传递Num(),因为拷贝了一份

传递结构体数组

我们来一个难度高的,其实也不难,我们可以传递一个结构体数组。

#include <stdio.h>

typedef struct {
  char *name;
  int age;
  char *gender;
}Girl;

void print_info(Girl *g, int size)
{
  int i;
  for (i=0;i<size;i++){
    printf("%s %d %s\n", g[i].name, g[i].age, g[i].gender);
  }
}
from ctypes import *

lib = CDLL("./mmp.dll")

class Girl(Structure):
    _fields_ = [
        ("name", c_char_p),
        ("age", c_int),
        ("gender", c_char_p),
    ]

g1, g2, g3 = Girl(c_char_p(b"mashiro"), 16, c_char_p(b"female")),              Girl(c_char_p(b"satori"), 17, c_char_p(b"female")),              Girl(c_char_p(b"koishi"), 16, c_char_p(b"female"))
g = (Girl * 3)(*[g1, g2, g3])

# 指定返回值类型
lib.print_info.restype = (Girl * 3)
lib.print_info(g, 3)
"""
mashiro 16 female
satori 17 female
koishi 16 female
"""

因此我们发现对于数组的传递也是很简单的

原文地址:https://www.cnblogs.com/traditional/p/12242009.html

时间: 2024-10-05 19:41:55

ctypes给扩展模块中的函数传递数组和结构体的相关文章

ctypes给扩展模块中的函数传递回调函数

C语言中的回调函数 什么是回调函数我就不介绍了,我们先来看看C语言中如何使用回调函数. 函数指针 不过在看回调函数之前,我们先看看如何把一个函数赋值给一个变量.准确的说,是让一个指针指向一个函数,这个指针叫做函数指针.通常我们说的指针变量是指向一个整型.字符型或数组等变量,而函数指针是指向函数.函数指针可以像一般函数一样,用于调用函数.传递参数. #include <stdio.h> int add(int a, int b){ int c; c = a + b; return c; } in

ctypes获取扩展模块中函数的返回值

ctypes获取返回值 我们前面已经看到了,通过ctypes像扩展模块中的函数传参时是没有问题的,但是我们如何拿到返回值呢?我们之前都是使用printf直接打印的,但是这样显然不行,我们肯定是要拿到返回值去做一些别的事情的.那么我们看看如何使用ctypes获取函数的返回值. 获取整型返回值 int test1(int a, int b) { int c; c = a + b; return c; } void test2() { } 我们定义了两个函数,下面编译成dll文件,dll文件名叫做mm

Shell 向函数传递 数组

Shell 向函数传递 数组

数组和链表的区别以及数组和结构体的区别

1,数组和链表的区别? 链表和数组都叫可以叫做线性表, 数组又叫做顺序表,主要区别在于,顺序表是在内存中开辟一段连续的空间来存储数据,而且必须是相同类型的数据. 而链表是通过存在元素中的指针联系到一起的,每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域,链表既可以靠指针来连接多块不连续的的空间也可以用一段连续的空间, 在逻辑上形成一片连续的空间来存储数据. 两种数据结构各有各的好处,链表方便删除和插入,数组方便排序等. 数组从栈中分配空间, 对于程序员方便快速

C语言语法笔记 – 高级用法 指针数组 指针的指针 二维数组指针 结构体指针 链表 | IT宅.com

原文:C语言语法笔记 – 高级用法 指针数组 指针的指针 二维数组指针 结构体指针 链表 | IT宅.com C语言语法笔记 – 高级用法 指针数组 指针的指针 二维数组指针 结构体指针 链表 | IT宅.com C语言语法笔记 – 高级用法 指针数组 指针的指针 二维数组指针 结构体指针 链表 本文由 arthinking 发表于315 天前 ⁄ itzhai.com原创文章 ⁄ C语言 ⁄ 评论数 3 ⁄ 被围观 1,775 views+ 指针数组: 在一个数组中,如果它的元素全部都是指针类

柔性数组(结构体最后一个域为0/1数组)

结构体最后的长度为0或1数组的作用(转载) 2012-05-07 17:07:09 其实很 早在看LINUX下就看到这个东西,后来在MFC内存池里同样也看到了类似的东西,还依照MFC写过一个类似的小内存池,(MFC用的是return this + 1)后来在李先静的<系统程序员成长计划>里看到了类似的定义,于是心里想着总结一下,结果发现网上已经有牛人总结的很好了,于是乎就转了过来,谢谢你们 的分享,这是我前进的动力!同时,需要引起注意的:ISO/IEC 9899-1999里面,这么写是非法的,

关于数组、结构体的初始化{0}

关于数组.结构体的初始化 一直以来,初始化变量和数组常采用定义时赋值的方法,今天在定义一个结构体的时候发现了一些问题,查了下相关资料发现以往的使用确实有些误区,一直没有注意到,于是搜集了下零散的资料在此记录一下. 一.以往常用的初始化方式: 1 int a=0; /*a初始化为0*/ 2 int b[10]={0}; /*b中全部元素初始化为0*/ 想必一直这样使用也确实不会发现问题,按照惯性思维,把0换成1就是把全部元素初始化为1了!然而事实却并非如此,请看下面这段代码↓ 1 #include

构造类型:数组\枚举\结构体

#import <Foundation/Foundation.h>//结构体:构造类型,是一种自定义类型.//struct CPoint//struct 是关键字用来声明结构体 后面是结构体的名字 花括号里面的内容叫成员变量//{//    float x;//    float y;// //};// 以分号结尾 typedef struct car{////    char name[20];////    int number;////    float score;////}Ca;//

Delphi - 数组和结构体

技术交流,DH讲解. 记得很早之前我就说过,数组和结构体在内存中其实一样的,他们都是连续分布的.例如: ? 1 2 3 4 TMyStruct = record   A,B,C:Integer; end; T3IntArray = array[0..2]of Integer; 这两个都占12字节,而且TMyStruct.A就是T3IntArray[0].而我们知道在访问数组中某个元素的时候,只是在第一个元素的地址 + 序号 * 元素大小.那么访问结构体应该也是这样的,只是结构体中每个元素的大小不