Python调用C的基础学习(传递数字、字符串、数组(一维、二维)、结构体)

本文详细介绍了如何在Python中使用ctypes模块调用Windows下的DLL文件,包括加载DLL、调用DLL中的方法、数据类型映射及具体示例,如数字、字符串、数组和结构体的传递与返回。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一: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平台为例子)。

ctypes官方文档

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_charchar
c_shortshort
c_intint
c_longlong
c_ulongunsign long
c_floatfloat
c_doubledouble
c_void_pvoid

对应的指针类型是在后面加上”_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转换)

python ctypes库3_如何传递并返回一个数组

python调用C++,传递结构体与结构体指针,以及嵌套结构体

ctypes库

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值