一:Python调用windows下DLL
注:我使用的环境:anaconda的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、传递并返回一维数组
C部分:
DLLEXPORT double* szTest_double(double *a, int nLen){
int i;
printf("in C:a[i] \t");
for(i = 0; i<nLen; i++){
printf("%.3lf ", a[i]);
a[i]+=2.0;
}
printf("\n");
return a;
}
Python部分:
print ("传递并返回一维数组")
INPUT = c_double * 10
data = INPUT()
for i in range(10):
data[i] = i*2.0
func.szTest_double.restype = POINTER(c_double)
result=func.szTest_double(data,len(data))
print('返回值:a[i]+2.0')
for i in range(10):
print(result[i])
运行截图:
4、传递二维数组
C部分:
//使用double a[][3]可行,即二维数组作为参数,第二维显示指定。而使用double **a不可用
DLLEXPORT double** szSecTest_double(double a[][3], int iLen,int jLen){
int i,j;
double **b;
b=(double **)malloc((iLen)*sizeof(double*));
printf("in C:a[i][j] \t");
for(i = 0; i<iLen; i++){
b[i]=(double *)malloc((jLen)*sizeof(double));
for(j=0;j<jLen;j++){
printf("%.3lf ", a[i][j]);
b[i][j]=a[i][j]+2.0;
}
printf("\n");
}
return b;
}
Python部分:
#在C中定义需要规定列数的限制,eg:double a[][3],不能为如:double **a的形式
print ("传递并返回二维数组")
SZ1=c_double *3
SZ2=SZ1*2
data=SZ2()
for i in range(2):
for j in range(3):
data[i][j]=(i+j)*1.0
func.szSecTest_double.restype =POINTER(POINTER(c_double))
result=func.szSecTest_double(data,2,3)
print('返回值:a[i][j]+2.0')
for i in range(2):
for j in range(3):
print(result[i][j])
运行截图:
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 ("传递结构体并返回结构体指针:\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])
运行截图:
6、传递并返回嵌套结构体
C部分:
struct parameter
{
int soler;
double gamma; /* for poly/rbf/sigmoid */
};
struct model
{
struct parameter param;
int size;
double arr[3];
};
// 传递并返回嵌套结构体
DLLEXPORT struct model* testStruct_Nest(struct model *input){
int i,j;
struct model *result_;
result_=(struct model*)malloc(sizeof(struct model));
result_->param.gamma=input->param.gamma+2;
result_->param.soler=input->param.soler+2;
result_->size=input->size+2;
for(i=0;i<3;i++)
result_->arr[i]=input->arr[i]+2;
return result_;
}
Python部分:
print ("传递并返回嵌套结构体指针")
class subStruct(Structure):
_fields_ = [("soler", c_int),
("gamma", c_double)]
class fatherStruct(Structure):
_fields_ = [("param", subStruct),
("size", c_int),
("arr", c_double * 3)]
fs=fatherStruct()
fs.size=5
for i in range(3):
fs.arr[i]=i*1.5
fs.param.soler=6
fs.param.gamma=7.0
func.testStruct_Nest.restype = POINTER(fatherStruct)
fs_result = func.testStruct_Nest(byref(fs))
print ("传递值:param.soler:6 param.gamma:7.0 size:5 arr:0.0 1.5 3.0")
print("返回值为:原值+2:","param.soler: ",fs_result.contents.param.soler,' param.gamma:',fs_result.contents.param.gamma,
' size:',fs_result.contents.size,' arr:',fs_result.contents.arr[0],fs_result.contents.arr[1],fs_result.contents.arr[2])
运行截图:
#当结构体中出现指针类型时
class Problem_Py(Structure):
_fields_ = [("num", c_int),
("G", POINTER(c_double))]
# prob,传递给C的参数
pro_py = Problem_Py()
pro_py.num=num
pro_py.G = cast(arr, POINTER(c_double))#arr为可传给C的形式,把其“赋值给”struct中G项的指针类型
参考以下文章:
Python的学习(三十二)---- ctypes库的使用整理
python调用c/c++时string的传入与返回深入分析
ctypes 加载的so库中函数参数的字符串传递问题(str与bytes转换)