• python与C语言调用模块 ctypes的详解


    ctypes

    ctypes是python的一个函数库,提供和C语言兼容的数据类型,可以直接调用动态链接库中的导出函数。
    为了使用ctypes,必须依次完成以下步骤:

    • 加载动态链接库
    • 将python对象转换成ctypes所能识别的参数
    • 使用ctypes所能识别的参数调用动态链接库中的函数

    动态链接库加载方式有三种:

    • cdll
    • windll
    • oledll

    它们的不同之处在于:动态链接库中的函数所遵守的函数调用方式(calling convention)以及返回方式有所不同。
    cdll用于加载遵循cdecl调用约定的动态链接库,windll用于加载遵循stdcall调用约定的动态链接库,oledllwindll完全相同,只是会默认其载入的函数统一返回一个Windows HRESULT错误编码。

    函数调用约定:函数调用约定指的是函数参数入栈的顺序、哪些参数入栈、哪些通过寄存器传值、函数返回时栈帧的回收方式(是由调用者负责清理,还是被调用者清理)、函数名称的修饰方法等等。常见的调用约定有cdecl和stdcall两种。在《程序员的自我修养--链接、装载与库》一书的第10章有对函数调用约定的更详细介绍。  
    cdecl规定函数参数列表以从右到左的方式入栈,且由函数的调用者负责清除栈帧上的参数。stdcall的参数入栈方式与cdecl一致,但函数返回时是由被调用者自己负责清理栈帧。而且stdcall是Win32 API函数所使用的调用约定。  
    

    例子:

    Linux下:

    或者:

    其他例子:


    一个完整的例子:

    1,编写动态链接库

    // filename: foo.c
    
    #include "stdio.h"
    
    char* myprint(char *str)
    {
        puts(str);
        return str;
    }
    
    float add(float a, float b)
    {
        return a + b;
    }

    将foo.c编译为动态链接库:
    gcc -fPIC -shared foo.c -o foo.so

    2.使用ctypes调用foo.so

    #coding:utf8
    
    #FILENAME:foo.py
    
    from ctypes import *
    
    foo = CDLL('./foo.so')
    
    myprint = foo.myprint
    myprint.argtypes = [POINTER(c_char)] # 参数类型为char指针
    myprint.restype = c_char_p # 返回类型为char指针
    res = myprint('hello ctypes')
    print(res)
    
    add = foo.add
    add.argtypes = [c_float, c_float] # 参数类型为两个float
    add.restype = c_float # 返回类型为float
    print(add(1.3, 1.2))

    执行:

    [jingjiang@iZ255w0dc5eZ test]$ python2.6 foo.py 
    hello ctypes
    hello ctypes
    2.5

    ctypes数据类型和C数据类型对照表


    查找动态链接库

    >>> from ctypes.util import find_library
    >>> find_library("m")
    'libm.so.6'
    >>> find_library("c")
    'libc.so.6'
    >>> find_library("bz2")
    'libbz2.so.1.0'

    函数返回类型

    函数默认返回 C int 类型,如果需要返回其他类型,需要设置函数的 restype 属性。

    >>> from ctypes import *
    >>> from ctypes.util import find_library
    >>> libc = cdll.LoadLibrary(find_library("c"))
    >>> strchr = libc.strchr
    >>> strchr("abcdef", ord("d"))
    -808023673
    >>> strchr.restype = c_char_p
    >>> strchr("abcdef", ord("d"))
    'def'
    >>> strchr("abcdef", ord("x"))

    回调函数

    • 定义回调函数类型,类似于c中的函数指针,比如:void (*callback)(void* arg1, void* arg2),定义为:callack = CFUNCTYPE(None, cvoidp, cvoidp)
      None表示返回值是void,也可以是其他类型。剩余的两个参数与c中的回调参数一致。
    • 定义python回调函数:
    def _callback(arg1, arg2):
        #do sth
        # ...
        #return sth
    • 注册回调函数:
    cb = callback(_callback)

    另外,使用ctypes可以避免GIL的问题。

    一个例子:

    //callback.c
    
    #include "stdio.h"
    
    void showNumber(int n, void (*print)())
    {
        (*print)(n);
    }

    编译成动态链接库:
    gcc -fPIC -shared -o callback.so callback.c

    编写测试代码:

    #FILENAME:callback.py
    
    from ctypes import *
    
    _cb = CFUNCTYPE(None, c_int)
    
    def pr(n):
        print 'this is : %d' % n 
    
    cb = _cb(pr)
    
    callback = CDLL("./callback.so")
    showNumber = callback.showNumber
    showNumber.argtypes = [c_int, c_void_p]
    showNumber.restype = c_void_p
    
    for i in range(10):
        showNumber(i, cb)

    执行:

    $ python2.7 callback.py 
    this is : 0 
    this is : 1 
    this is : 2 
    this is : 3 
    this is : 4 
    this is : 5 
    this is : 6 
    this is : 7 
    this is : 8 
    this is : 9 

    结构体和联合

    union(联合体 共用体)
    1、union中可以定义多个成员,union的大小由最大的成员的大小决定。 
    2、union成员共享同一块大小的内存,一次只能使用其中的一个成员。 
    3、对某一个成员赋值,会覆盖其他成员的值(也不奇怪,因为他们共享一块内存。但前提是成员所占字节数相同,当成员所占字节数不同时只会覆盖相应字节上的值,>比如对char成员赋值就不会把整个int成员覆盖掉,因为char只占一个字节,而int占四个字节)
    4、联合体union的存放顺序是所有成员都从低地址开始存放的。
    

    结构体和联合必须从Structure和Union继承,子类必须定义__fields__属性,__fields__属性必须是一个二元组的列表,包含field的名称和field的类型,field类型必须是一个ctypes的类型,例如:c_int, 或者其他继承自ctypes的类型,例如:结构体,联合,数组,指针。

    from ctypes import *
    
    class Point(Structure):
        __fields__ = [         ("x", c_int),
            ("y", c_int),
        ]   
    
        def __str__(self):
            return "x={0.x}, y={0.y}".format(self)
    
    point1 = Point(x=10, y=20)
    print "point1:", point1
    
    class Rect(Structure):
        __fields__ = [ 
            ("upperleft", Point),
            ("lowerright", Point),
        ]   
    
        def __str__(self):
            return "upperleft:[{0.upperleft}], lowerright:[{0.lowerright}]".format(self)
    
    rect1 = Rect(upperleft=Point(x=1, y=2), lowerright=Point(x=3, y=4))
    print "rect1:", rect1

    运行:

    python test.py 
    point1: x=10, y=20
    rect1: upperleft:[x=1, y=2], lowerright:[x=3, y=4]

    数组

    数组定义很简单,比如:定义一个有10个Point元素的数组,
    TenPointsArrayType = Point * 10
    初始化和使用数组:

    from ctypes import *
    
    TenIntegersArrayType = c_int * 10
    array1 = TenIntegersArrayType(*range(1, 11))print array1
    
    for i in array1:
        print i

    运行:

    $ python2.7 array.py
    <__main__.c_int_Array_10 object at 0x7fad0d7394d0>
    1   
    2   
    3   
    4   
    5   
    6   
    7   
    8   
    9   
    10  

    指针

    pointer()可以创建一个指针,Pointer实例有一个contents属性,返回指针指向的内容。

    >>> from ctypes import *
    >>> i = c_int(42)
    >>> p = pointer(i)
    >>> p<__main__.LP_c_int object at 0x7f413081d560>
    >>> p.contents
    c_int(42)
    >>>

    可以改变指针指向的内容

    >>> i = c_int(99)
    >>> p.contents = i
    >>> p.contents
    c_int(99)

    可以按数组的方式访问,并改变值

    >>> p[0]
    99
    >>> p[0] = 22
    >>> i
    c_int(22)

    传递指针或引用

    很多情况下,c函数需要传递指针或引用,ctypes也完美支持这一点。
    byref()用来传递引用参数,pointer()也可以完成同样的工作,但是pointer会创建一个实际的指针对象,如果你不需要一个指针对象,用byref()会快很多。

    >>> from ctypes import *
    >>> i = c_int()
    >>> f = c_float()
    >>> s = create_string_buffer('00' * 32) >>> print i.value, f.value, repr(s.value)
    0 0.0 ''
    >>> libc = CDLL("libc.so.6")
    >>> libc.sscanf("1 3.14 Hello", "%d %f %s", byref(i), byref(f), s)
    3   
    >>> print i.value, f.value, repr(s.value)
    1 3.1400001049 'Hello'

    可改变内容的字符串

    如果需要可改变内容的字符串,需要使用 createstringbuffer()

    >>> from ctypes import *
    >>> p = create_string_buffer(3)      # create a 3 byte buffer,  initialized to NUL bytes
    >>> print sizeof(p), repr(p.raw)
    3 '/x00/x00/x00'>>> p = create_string_buffer("Hello")      # create a buffer containing a NUL terminated string
    >>> print sizeof(p), repr(p.raw)
    6 'Hello/x00'
    >>> print repr(p.value)
    'Hello'
    >>> p = create_string_buffer("Hello", 10)  # create a 10 byte buffer
    >>> print sizeof(p), repr(p.raw)
    10 'Hello/x00/x00/x00/x00/x00'
    >>> p.value = "Hi"
    >>> print sizeof(p), repr(p.raw)
    10 'Hi/x00lo/x00/x00/x00/x00/x00'
    >>> 

    赋值给c_char_pc_wchar_pc_void_p

    只改变他们指向的内存地址,而不是改变内存的内容

    >>> s = "Hello, World"
    >>> c_s = c_char_p(s)
    >>> print c_s
    c_char_p('Hello, World')>>> c_s.value = "Hi, there"
    >>> print c_s
    c_char_p('Hi, there')
    >>> print s                 # first string is unchanged
    Hello, World
    >>> 

    数据都可以改变

    >>> i = c_int(42)
    >>> print i
    c_long(42)
    >>> print i.value42
    >>> i.value = -99
    >>> print i.value
    -99
    >>>

    使用中遇到的一些问题

    1:当动态库的导出函数返回char *的时候,如何释放内存
    如果把restype设置为c_char_p,ctypes会返回一个常规的Python字符串对象。一种简单的方式就是使用void *和强制转换结果。

    string.c:
    
    #include 
    #include 
    #include 
    
    char *get(void)
    {
        char *buf = "Hello World";
        char *new_buf = strdup(buf);
        printf("allocated address: %p
    ", new_buf);
        return new_buf;
    }
    
    void freeme(char *ptr)
    {
        printf("freeing address: %p
    ", ptr);
        free(ptr);
    }

    Python使用:

    from ctypes import *
    
    lib = cdll.LoadLibrary('./string.so')
    lib.freeme.argtypes = c_void_p,
    lib.freeme.restype = None
    lib.get.argtypes = []
    lib.get.restype = c_void_p
    
    >>> ptr = lib.get()
    allocated address: 0x9facad8
    >>> hex(ptr)
    '0x9facad8'
    >>> cast(ptr, c_char_p).value
    'Hello World'
    >>> lib.freeme(ptr)
    freeing address: 0x9facad8

    也可以使用c_char_p的子类,因为ctypes不会对简单类型的子类调用getfunc

    class c_char_p_sub(c_char_p):
        pass
    
    lib.get.restype = c_char_p_sub

    value属性会返回字符串。在这个例子中,可以把freeme的参数改为更通用的c_void_p,它接受任何指针类型或整型地址。

    2:如何把含有‘’的char*转换成python字符串
    How do you convert a char * with 0-value bytes into a python string?


    参考资料

  • 相关阅读:
    socket错误码获取
    代码整洁之道读书笔记函数
    算法学习之堆排序
    包含与继承区别
    提高 LayerBacked Memory Use
    RenderBuffer
    算法学习之快速排序
    NSTimer
    DNS and BIND ... (转载) zhumao
    Samba学习笔记(转载) zhumao
  • 原文地址:https://www.cnblogs.com/hls91/p/13999526.html
Copyright © 2020-2023  润新知