美文网首页
Python ctypes的简单使用

Python ctypes的简单使用

作者: 最后一个前锋 | 来源:发表于2019-04-18 03:47 被阅读0次

    1 背景

    上星期申请了Prof. Anthoniou的实验室做HiWi,进入Vanpooling项目这个组,负责帮忙实现Enhancement Demand Model (Python)和Scheduler (C++)之间的交互。虽然说是交互,其实就是需要将Demand Model输出的结果作为函数参数通过一定的方式传入Scheduler中进行计算。期间尝试了多种方法:

    • 利用SWIG来将写好的Scheduler翻译成Python的一个module,然后在Python里直接调用。但是由于两种语言在各种数据类型的定义上存在区别,所以有一些参数很难用Python定义的变量带入,比如说指针类型,甚至字符串类型。
    • 利用C++的拓展包Boost.Python来定义Scheduler需要的参数,这样就能够将Python定义的变量直接使用
      (同样需要用SWIG先翻译再导入到Python中)。但是Boost的安装摸了半天还是没能实现一个最起码的Hello, world!程序,所以只好暂时先放放另谋出路,不过以后还是会继续学习这个包的。
    • 利用Python里的ctypesmodule来实现。因为仔细想了想,我们所需要做的像防御只是将Python的数据类型转化为C++所能接受的数据类型传入到Scheduler里进行计算。而ctypes本来就是专门为此而生的,所以这是最简单直接的方法。

    从而便花了一点时间去简单地学习了下如何利用ctypes去在Python中定义出可以传给C++编写的程序做实际参数的变量。官方所给的说明文档虽然很好很全面,但是给的例子还是太少,好在还是发现了一篇总结得不错的,给出很多有用例子的博文A
    备注:以下操作所用工具MinGWPython3.6

    2 利用ctypes的大概流程

    其实ctypes是设计来方便PythonC之间的交互的,而不是用来实现PythonC++数据类型的转化的,但是C++也是有方法将自身用C来进行编译的,只需要简单的利用下面的语句就能够实现。

    extern "C" { // 注意一下C好像必须要用大写
    ...这里是代码...
    }
    
    1. 首先要用命令行工具cd到程序文件所在的文件夹。
    2. 然后,要将程序文件编译为.dll文件
    • 如果用的是C来写的程序,假设程序名称为test.c,则执行:
    gcc -shared -fPIC test.c -o test.dll
    
    • 如果用的是C++来写的程序,假设程序名称为test.cpp,则执行:
    g++ -shared -fPIC test.cpp -o test.dll
    
    1. 最后在Python的程序文件中将其导入即可。
    from ctypes import *
    test = CDLL('test.dll') #test便是程序的一个实例,可以对C或者C++程序里所定义的函数进行调用
    

    3 一些数据类型的转化

    当然,关键的还是如何在Python里能够定义出C++的数据类型以便于将其传入到C++里进行计算。由于我们用到的是C++的程序,所以下面的例子都是关于在Python里实现C++数据类型的实现的。

    3.1 官方给出的基础转换表

    一些基础数据类型可以直接对照下表利用ctypesPython中定义出相应的C++的数据类型 (C语言和C++的基础数据类型好像是一样的?)。

    官方文档中给出的基础数据类型对照表
    下面给出一个简单的例子。要先说明一个需要注意的问题是,在Python程序中调用函数时,我们要为手动为函数设定函数的参数类型,如果是有返回值的函数还需要设置返回值的类型,因为按照博文A的说法:

    出现此问题的原因在于DLL文件无法在调用其中函数时自动设定数据类型,而如果不对类型进行设定,则调用函数时默认的输入、输出类型均为int。故如果函数的参数或返回值包含非int类型时,就需要对函数的参数以及返回值的数据类型进行设定。

    而本人也尝试过,确实是需要的,否则会有错误提示。但是似乎如果是结构体变量,或者指针变量作为输入时则不需要。

    test.cpp

    #include <iostream>
    using namespace std;
    
    extern "C" {
        char py2c(int x, double y, char z);
    }
    
    char py2c(int x, double y, char z){
        cout << x << " belong to int type!" << endl;
        cout << y << " belong to double type!" << endl;
        cout << z << " belong to char type!" << endl;
        cout << "x + y = " <<endl;
        return x+y;
    }
    

    test.py

    from ctypes import *
    test = CDLL('test.dll')
    _x = c_int(1)
    _y = c_double(1)
    _z = c_char(b'z')
    test.py2c.argtypes = (c_int, c_double, c_char)
    test.py2c.restypes = c_char
    test.py2c(_x, _y, _z)
    

    3.2 字符串和字符串数组类型,其它的数据类型的数组以及二维数组

    • 字符串,整型数组,浮点型数组
    str1 = (c_char*3)() # 生成一个长度为3,且全部为None的字符串
    str2 = (c_char*4)('a', 'b', 'c') #生成一个长度为4,且前三个字符为abc,最后一个为None的字符串
    int1 = (c_int*5)()
    float1 = (c_double*6)()
    
    • 字符串数组、二维数组
      其实字符串数组就是靠二维数组来实现的
    (c_int*5)*3 # 生成5*3的二维数组
    ((c_int*6)*5)((1,2,3,4,5), (6,6,6), (7,7,7)) # 生成6*5的数组并进行初始化,其余未初始化的元素为0
    ((c_char*5)*3)() # 生成一个字符串数组!!!
    

    字符串数组的赋值需要用到create_string_buffer这个函数

    strArr = ((c_char*5)*3)()
    charList = ['aaa', 'bbb', 'ccc']
    for row in range(3):
    # create_string_buffer的第一个参数是赋值的内容,第二个参数是数组中每一个字符串的长度
    # 而且这个数字必须和赋值的对象长度相同,比如这里根据定义strArr没一个字符串长度都是5,
    # 所以create_string_buffer的第二个参数也必须是5
        strArr[row] = create_string_buffer(charList[row].encode(), 5)
    for row in strArr:
        print(row.value.decode())
    

    其中的encode是用于将string转为bytes,而decode则相反。

    4 类方法的调用

    这里完全是将博文A里的例子给搬过来了,因为觉得确实是个不错的方法。像下面这样定义函数就能够直接对hello()函数进行调用了。

    extern "C" {
        void hello();
    }
    class Test{
        public:
            void hello();
    }; // 别漏了分号
    void Test::hello(){
        printf("Hello!");
    }
    void hello(){
        Test *testP = new Test;
        testP->hello();
        delete testP;
    }
    

    5 指针类型

    ctypes里有三个方法来构建一个变量的指针,分别是byref, pointer, POINTER。其中byref,pointer的对象都只能是ctypes数据类型的,但是好像我用byref来取结构体变量的地址的时候会报错,而用pointer则没有报错,不知道为什么。如果使用的是pointer,则在输出它所指地址内包含的值时必须先用contents这个方法。'POINTER'则是用来创建一个地址类型的,而不是用来取址的。

    x = pointer(c_int(3))
    print(x.contents.value)
    y = byref(c_int(4))
    intPointer = POINTER(c_int())
    

    6 结构体

    这个就直接给例子吧,自己参悟。
    test.cpp

    #include<string>
    #include<iostream>
    
    struct Location {
        int node; 
    };
    
    struct Demand {
        struct Location *pickup;
        struct Location stops[5]; 
        double real_distance;
    };
    
    typedef double SPEED;
    typedef SPEED **TS;
    
    extern "C"{
        void handle(Demand *demand, TS ts);
    }
    void handle(Demand *demand, TS ts){
        std::cout<<"Done!"<<std::endl;
    }
    

    test.py

    from ctypes import *
    
    class Location(Structure):
        _fields_ = (
            ('id', c_int),
            ('node', c_int)
        )   
    
    class Demand(Structure):
        _fields_ = (
            ('pickup', POINTER(Location)),
            ('stops', Location*5),
            ('real_distance', c_double)
        )
    pickup_loc = Location(23)
    _stops = (Location*5)()
    demands = Demand(pointer(pickup_loc),_stops,10.2)
    ts = c_double(45.6)
    TSPointer = POINTER(c_double)
    
    test = CDLL('test.dll')
    test.handle(pointer(demands), TSPointer(TSPointer(ts)))
    

    上面的例子是我从写的程序里删减部分后拷贝过来的,应该覆盖了大部分的转换需求,但是我也只是现学不久,可能有些地方有些问题。

    相关文章

      网友评论

          本文标题:Python ctypes的简单使用

          本文链接:https://www.haomeiwen.com/subject/plqawqtx.html