在之前的文章Python调用C/C++动态链接库中,举例说明了Python访问C指针所指向数据的方式,主要是从指针所指向的内存中取数据。今天遇到的问题是,如何将Python产生的数组赋值给C指针。
下面用一个小例子来说明一下。
C从外部获取指针所指向的数据,并通过print_array()函数打印出来,C代码:
- #include "stdio.h"
- #include
-
- extern "C"
- {
-
- using namespace std;
-
- // 创建一个长度为len的int型指针
- int *get_pointer(int len)
- {
- int *ptr = new int[len];
- return ptr;
- }
-
- // 打印指针数据,len为打印的int型数据的长度
- void print_array(int *array_in, int len)
- {
- for(int i = 0; i < len; i++)
- {
- printf("array_in[%d] = %d \n", i, array_in[i]);
- }
- }
-
- // 释放指针
- void free_pointer(int *ptr)
- {
- if(ptr)
- {
- delete [] ptr;
- }
- }
-
-
- }
编译C动态库:
g++ -std=c++11 python2c.cpp -o libpy2c.so -shared -fPIC
注意,不要加-c选项,否则编译出来的动态库加载时会报错:
only ET_DYN and ET_EXEC can be loaded
C动态库有了,接下来通过Python产生数据,并传递给C指针。传递数据的方式目前试用了两种,都能work。
- # -*- coding: utf-8 -*-
- from ctypes import *
- import numpy as np
-
- lib_path = r'./libpy2c.so'
- solib = cdll.LoadLibrary(lib_path)
-
- # Create C pointer
- c_len = 10
- solib.get_pointer.argtypes = [c_int]
- solib.get_pointer.restype = POINTER(c_int)
- ptr = solib.get_pointer(c_len)
-
- print("ptr = {}".format(ptr))
-
-
- # Create a python array
- py_len = 10
- py_array = [i for i in range(py_len)]
- py_array = np.array(py_array).astype(np.int32)
- for i in range(py_len):
- print(py_array[i])
-
- # Method 1
- # 方法1,逐个元素拷贝,将Python数组逐个拷贝到ctypes pointer所指向的内存中。
- for i in range(py_len):
- ptr[i] = py_array[i]
-
- '''
- # Method 2
- # 方法2,通过cast方法将Python数组数据转换成指针形式。
- py_array = np.ascontiguousarray(py_array, dtype=np.int32)
- ptr = cast(py_array.ctypes.data, POINTER(c_int))
- print("ptr = {}".format(ptr))
- '''
-
- # Call C shared library to print pointer
- solib.print_array.argstype = [POINTER(c_int), c_int]
- solib.print_array.restype = c_void_p
- solib.print_array(ptr, py_len)
-
- solib.free_pointer.argstype = [POINTER(c_int)]
- solib.free_pointer.restype = c_void_p
- solib.free_pointer(ptr)
执行结果:
从以上结果中可以看到,数据成功地从Python数组传递给了C指针。
将Python代码中的方法1关闭,方法2打开:
执行结果与第一种方法一致,都能成功将Python的数组数据传递给C语言的指针。
如果在Python中创建的数组大小大于C语言为指针分配的内存大小,情况如何呢?在Python代码中,我们保持c_len=10不变,将py_len改为20,测试两种方法。
为了截图方便,我们把Python中的数组打印关闭。
方法1结果:
虽然完成了数据传递,但是出现了内存越界。
我们再来看方法2:
完成了数据传递,而且一切正常,从图上cast前后ptr值的变化来看,指针变量ptr实际上是指向了一个新的地址,也就是通过cast转换而得到的数据的地址。因此,这时不会再向原指针地址写数据,也就不会受原指针内存空间的限制。
但以上代码这样写是有问题的,因为通过C开辟出来的空间是无法被释放的,因为最后传入free_pointer()的地址变化了。所以如果使用cast方式,由于是通过Python来创建指针,因此C库中的get_pointer()函数就无需调用了。