Python调用C的基础学习(传递数字、字符串、数组(一维、二维)、结构体)
一:Python调用windows下DLL
注:我使用的环境:IDLE (Python 3.6 32-bit) ;vs2010;Windows
1、如何使用vs2010生成dll
参见1~4步:https://jingyan.baidu.com/article/5bbb5a1bd4a7bd13eaa17968.html
注:生成的dll文件在建立的项目debug文件夹下,如下图所示:
需注意vs和Python的位数相同(如:同为32位)
在python中某些时候需要C做效率上的补充,在实际应用中,需要做部分数据的交互。使用python中的ctypes模块可以很方便的调用windows的dll(也包括linux下的so等文件),下面将详细的讲解这个模块(以windows平台为例子)。
2、加载DLL
- 访问dll,首先需引入ctypes库(from ctypes import * )
DLL有stdcall调用约定和cdecl调用约定声明的导出函数,在使用python加载时使用的加载函数是不同的
调用约定的介绍:详见:带你玩转Visual Studio——调用约定__cdecl、__stdcall和__fastcall
1)stdcall调用约定:两种加载方式
Objdll = ctypes.windll.LoadLibrary("dllpath")
Objdll = ctypes.WinDLL("dllpath")
2)cdecl调用约定:也有两种加载方式
Objdll = ctypes.cdll.LoadLibrary("dllpath")
Objdll = ctypes.CDLL("dllpath")
//其实windll和cdll分别是WinDLL类和CDll类的对象。
3、调用dll中的方法
在2中加载dll的时候会返回一个DLL对象(假设名字叫Objdll),利用该对象就可以调用dll中的方法。
e.g.如果dll中有个方法名字叫Add(注意如果经过stdcall声明的方法,如果不是用def文件声明的导出函数或者extern “C” 声明的话,编译器会对函数名进行修改,这个要注意,我想你们懂的。)
调用:nRet = Objdll.Add(12, 15) 即完成一次调用。
看起来调用似乎很简单,不要只看表象,呵呵,这是因为Add这个函数太简单了,现在假设函数需要你传入一个int类型的指针(int*),可以通过库中的byref关键字来实现,假设现在调用的函数的第三个参数是个int类型的指针。
intPara = c_int(9)
dll.sub(23, 102, byref(intPara))
print intPara.value
- 函数参数申明,通过设置函数的argtypes属性
- 函数返回类型,函数默认返回c_int类型,如果需要返回其他类型,需要设置函数的restype属性
4、C基本类型和ctypes中实现的类型映射表
ctypes数据类型 | C数据类型 |
---|---|
c_char | char |
c_short | short |
c_int | int |
c_long | long |
c_ulong | unsign long |
c_float | float |
c_double | double |
c_void_p | void |
对应的指针类型是在后面加上”_p”,如int*是c_int_p等等。
在python中要实现c语言中的结构,需要用到类。
5. 指针与引用
常用的通过调用ctypes类型的指针函数来创建指针实例:
from ctype import *
i = c_int(1)
pi = POINTER(i)
对指针实例赋值只会改变其指向的内存地址,而不是改变内存的内容,与其他语言类似,如需要可改变内容的字符串,可须使用create_string_buffer()
>>> p = create_string_buffer("Hello", 10) # create a 10 byte buffer
>>> print sizeof(p), repr(p.raw)
10 'Hello/x00/x00/x00/x00/x00'
不带参数的调用指针类型创建一个NULL指针, NULL指针有一个False布尔值
>>> null_ptr = POINTER(c_int)()
>>> print bool(null_ptr)
False
指针实例有一个contents属性,返回这个指针所指向的对象。
另外,byref()是用来传递引用参数,pointer()作为传参通常会创建一个实际的指针对象,当不需要实际指针对象时,则可使用byref()
二:进行Python调用C的代码实现
注:下面是C中的头文件和声明
#include <stdio.h>
#include <string.h>
#include <stdlib.h >
#define DLLEXPORT __declspec(dllexport)
Python头文件部分和导入dll
from ctypes import *
func = CDLL("dll/first_dll.dll")
1、传递并返回数字
C部分:
//传递与返回数值
DLLEXPORT int add(int num1, int num2){
//printf("hhh\n");
return num1 + num2;
}
Python部分:
print ("传递并返回数字")
x=4
y=5
res_int = func.add(x,y)
print(x,' + ',y,' = ',res_int)
2、传递并返回字符串
使用ctypesj进行python调用c/c++时,当把string类型str直接传进c/c++的函数时,发现strlen(str)永远是1。例如当输入为"abcdefg"时,强行输出传入的char* s后面的几位s[0]、s[1]、s[2]、s[3]……时,发现输出是a b c d e f g,每个char输出后都会有个空格,然后我再输出(int)s[0]…..发现那些空格都是’\0’。因此strlen(str)是1原因很明显是因为其读到了’\0’就停下来了。而python的string的一个字符明显是占了2byte的,当字符为英文字母时,后1byte为0,前1byte储存ascII码,这估计是为了支持中文这些复杂的文字,而char*是按照1byte为单位进行读取和写入的,所以就有了上面的现象。
故可以把str类型的参数转换成bytes类型进行传递
C部分:
DLLEXPORT char* strTest(char* pVal){
return pVal;
}
Python部分:
print ("传递并返回字符串")
#字符串转化为byte传送 方法一
#val = bytes('qqqqqq',encoding='utf-8')
#方法二
str_test="need_bytes"
val=str_test.encode()
func.strTest.restype = c_char_p
print(func.strTest(val))
3、传递一维数组
注:可以将数组指针传递给dll,但无法返回数组指针,python中没有对应的数组指针类型。
如果需要返回数组,需借助结构体(详见结构体部分)
C部分:
DLLEXPORT int szTest(int a[], int nLen){
//int i; printf("一维数组:");
//for(i = 0; i<nLen; i++) printf("%d ", a[i]); printf("\n");
return a[nLen-1]+5;
}
Python部分:
print ("传递一维数组")
INPUT = c_int * 10
data = INPUT()
for i in range(10):
data[i] = i
result=func.szTest(data,len(data))
print('data[9] ',data[9],' 返回值result: data[9]+5 ',result)
4、传递二维数组
C部分:
DLLEXPORT int szSecTest(int a[2][3], int iLen,int jLen){
int i,j;
for(i = 0; i<iLen; i++){
for(j=0;j<jLen;j++)
printf("%d ", a[i][j]);
printf("\n");
}
return a[1][2]+5;
}
注:目前使用Python调用C的dll时,C中的printf并不能显示出来(我未找到解决办法,不知道是否有办法),故若想知道传递的数组是否正确可以,把其输出到文件file中(fprintf)
Python部分:
print ("传递二维数组")
SZ1=c_int *3
SZ2=SZ1*2
data=SZ2()
#data=[[i+j for i in range(2)] for j in range(2)] #使用它时,相当于list数组,会报错ctypes.ArgumentError: argument 1: <class 'TypeError'>: Don't know how to convert parameter 1
for i in range(2):
for j in range(3):
data[i][j]=i+j
#print(data)
result=func.szSecTest(data,2,3)
print('data[1][2] ',data[1][2],' 返回值result: data[1][2]+5 ',result)
5、传递并返回结构体
C部分:
typedef struct StructPointerTest{
char name[20];
int age;
int arr[3];
int arrTwo[2][3];
}StructPointerTest, *StructPointer;
// 传递结构体并返回结构体指针(把数组存在结构体中返回)
DLLEXPORT StructPointer testStruct(StructPointerTest input){
int i,j;
StructPointer p = (StructPointer)malloc(sizeof(StructPointerTest));
strcpy(p->name, strcat(input.name,"add"));
p->age =input.age + 5;
for(i=0;i<3;i++)
p->arr[i] =input.arr[i]+1;
for(i=0;i<2;i++)
for(j=0;j<3;j++)
p->arrTwo[i][j] =input.arrTwo[i][j]+2;
return p;
}
Python部分:
class StructPointer(Structure):
_fields_ = [("name",c_char * 20),
("age",c_int),
("arr", c_int * 3),
("arrTwo", (c_int * 3)*2)]
sp=StructPointer()
sp.name=bytes('joe_',encoding='utf-8')
sp.age=23
for i in range(3):
sp.arr[i]=i
for i in range(2):
for j in range(3):
sp.arrTwo[i][j]=i+j
func.testStruct.restype = POINTER(StructPointer)
p = func.testStruct(sp)
print ('*' * 20)
print ("传递并返回结构体:\n 传递值:name:joe_ age:23 arr:0 1 2 arrTwo:0 1 2;1 2 3")
print("返回值为:name+add:",p.contents.name,' age+5:',p.contents.age,
' arr+1:',p.contents.arr[0],p.contents.arr[1],p.contents.arr[2],
' arrTwo+2:',p.contents.arrTwo[0][0],p.contents.arrTwo[0][1],p.contents.arrTwo[0][2],
p.contents.arrTwo[1][0],p.contents.arrTwo[1][1],p.contents.arrTwo[1][2])
以上的运行效果:
参考以下文章:
Python的学习(三十二)---- ctypes库的使用整理
python调用c/c++时string的传入与返回深入分析