Python进阶教程m7c–混合编程–C语言接口ctypes(2)

原文链接: http://www.juzicode.com/archives/827

Python进阶教程m7b–C语言接口ctypes(1)一文中,介绍了利用ctypes标准库封装C函数的基本方法,怎么加载DLL文件,基本数据类型和数组类型,这篇文章将会讨论结构体、指针等问题。

4 结构体类型

ctypes对应C语言结构体数据类型,需要定义一个继承自Structrue的class,其中的成员变量定义在__field__中, __field__是由多个tuple组成的一个list,每个tuple表示一个结构体的成员变量,tuple中第0个元素和C语言中结构体的变量名称一致的str,tuple中第1个元素为对应到ctypes的数据类型。

下面的例子就是C语言struct和ctypes自定义“结构体”的对比:

初始化ctypes的“结构体”数据就和初始化类一样,在括号内传入初始化值即可,同时可以用“对象.属性“的方法访问新定义的“结构体”的成员:

from ctypes import *

class TsFruit(Structure):         #定义ctypes类型的“结构体”
    _fields_ = [('id', c_int), 
	            ('name', c_char*10),
	            ('weight', c_float),				
		]

fruit = TsFruit(10001,b'juzi',50) #初始化
print('id:',fruit.id)             #使用成员变量
print('name:',fruit.name)
print('weight:',fruit.weight)

下面的例子就是一个定义、初始化、使用的完整例子,C函数的入参是个 TS_FRUIT结构体,在C函数内部打印这个结构体的成员变量值。在Python中声明argtypes时用ctypes新定义的“结构体”类型class TsFruit :pyt.trans_struct.argtypes=(TsFruit,)。

typedef struct __ts_fruit {
	int id;
	char name[10];
	float weight;
}TS_FRUIT, *PTS_FRUIT;

__declspec(dllexport) int trans_struct(TS_FRUIT fruit)
{
	printf("id=%d \n", fruit.id);
	printf("name=%s \n", fruit.name);
	printf("weight=%f \n", fruit.weight);
	printf("\n");
	return 0;
}
from ctypes import *

class TsFruit(Structure):          #定义ctypes类型的“结构体”
    _fields_ = [('id', c_int), 
	            ('name', c_char*10),
	            ('weight', c_float),				
	            ]

fruit = TsFruit(10001,b'juzi',50) #初始化
print('id:',fruit.id)             #使用成员变量
print('name:',fruit.name)
print('weight:',fruit.weight)

pyt = CDLL('pytest.dll')          #加载dll
pyt.trans_struct.restype=c_int    #声明返回参数类型和入参类型
pyt.trans_struct.argtypes=(TsFruit,)
pyt.trans_struct(fruit)           #执行
运行结果:
id: 10001
 name: b'juzi'
 weight: 50.0
 id=10001
 name=juzi
 weight=50.000000

5、指针

5.1 基本数据类型

下面的例子是一个入参为int型指针的函数,函数内部对这个入参做加10操作。函数的入参声明应该用大写的POINTER(ctypes数据类型)也就是POINTER(c_int)表示,在调用函数的时候则用小写的pointer(变量):

__declspec(dllexport) int modify_i(int* x)
{
	printf("modify_i: before modify, x=%d \n", *x);
	*x = *x + 10;
	printf("modify_i: after  modify, x=%d \n", *x);
	return 0;
}
print('-----欢迎来到www.juzicode.com')
print('-----公众号: 桔子code/juzicode \n')   

from ctypes import *

pyt = CDLL('pytest.dll') 
print()
print('指针:int')    
pyt.modify_i.restype=c_int     
pyt.modify_i.argtypes=(POINTER(c_int),)
x = c_int(3)
pyt.modify_i(pointer(x))           
print('in python x=',x.value)
==========结果:
 指针:int
 modify_i: before modify, x=3
 modify_i: after  modify, x=13
 in python x= 13

同样的方法也适用float,long,double等参数类型。

5.2 数组类型

数组类型的参数形式,比如定义了一个100长度的int数据,在声明入参形式的时候,可以使用c_int*100或者POINTER(c_int),下面就是用这2种形式入参声明的例子。

__declspec(dllexport) int modify_i_array(int x[],int len)
{
	printf("modify_i: before modify, x[0]=%d \n", *x);
	for (int i=0; i < len; i++) {
		*(x + i) = 1000+ *(x + i);
	}
	printf("modify_i: after  modify, x[0]=%d \n", *x);
	return 0;
}
from ctypes import *

pyt = CDLL('pytest.dll')    
print()
print('指针:int数组, C函数内部加1000')    
c_int_100 = c_int * 100

pyt.modify_i_array.restype=c_int     
pyt.modify_i_array.argtypes=(c_int_100,c_int)
x = c_int_100(10,20,30,40,50,60)
pyt.modify_i_array(x,100)           
print('in python x[0]=',x[0])

pyt.modify_i_array.argtypes=(POINTER(c_int),c_int)
x = c_int_100(11,22,33,44,55,66)
pyt.modify_i_array(x,100)           
print('in python x[0]=',x[0])
==========结果:

指针:int数组, C函数内部加1000
modify_i: before modify, x[0]=10
modify_i: after  modify, x[0]=1010
in python x[0]= 1010
modify_i: before modify, x[0]=11
modify_i: after  modify, x[0]=1011
in python x[0]= 1011

5.3 单字符数据类型

在ctypes中为char类型的数据定义了一个新的指针类型c_char_p,所以在使用指针的时候可以用 c_char_p 或者用POINTER(c_char)声明:

__declspec(dllexport) int modify_c(char* x)
{
	printf("modify_c: before modify, x=%c \n", *x);
	*x = *x + 10;  //对传入的字符加10
	printf("modify_c: after  modify, x=%c \n", *x);
	return 0;
}
from ctypes import *

pyt = CDLL('pytest.dll')  

print()
print('指针: 修改单个字符,POINTER')
pyt.modify_c.restype=c_int     
pyt.modify_c.argtypes=(POINTER(c_char),)
x = c_char(b'a')
pyt.modify_c(pointer(x))           
print('in python c_char x=', x.value)

print('指针: 修改单个字符,c_char_p')
pyt.modify_c.restype=c_int     
pyt.modify_c.argtypes=(c_char_p,)
x =  b'a' 
pyt.modify_c(x)           
print('in python c_char x=', x)
==========结果:
指针: 修改单个字符,POINTER
modify_c: before modify, x=a
modify_c: after  modify, x=k
in python c_char x= b'k'
指针: 修改单个字符,c_char_p
modify_c: before modify, x=a
modify_c: after  modify, x=k
in python c_char x= b'k'

5.4 字符串

在C语言中字符串可以用字符指针表示,用c_char_p 或者用POINTER(c_char)声明 入参或返回值类型。当然下面的例子中在C函数中并没有检查指针的边界,并不是安全使用指针的方式,这个例子只是为了说明字符指针的用法。

__declspec(dllexport) int modify_c2(char* x)
{
	printf("modify_c2: before modify, x=%s\n", x);
	strcpy(x, "juzicode.com"); //将出入的字符串修改为特定字符串
	printf("modify_c2: after  modify, x=%s \n", x);
	return 0;
}
print('指针: 修改字符串,POINTER ')
pyt.modify_c2.restype=c_int     
pyt.modify_c2.argtypes=(POINTER(c_char),)
x =  b'abcdefghijklmno'
pyt.modify_c2(x)           
print(x)

print('指针: 修改字符串,c_char_p ')
pyt.modify_c2.restype=c_int     
pyt.modify_c2.argtypes=(c_char_p,)
x =  b'ABCDEFGHIJKLMNO'
#x =  b'abcdefghijklmno'
pyt.modify_c2(x)           
print(x)
==========结果:

指针: 修改字符串,POINTER
modify_c2: before modify, x=abcdefghijklmno
modify_c2: after  modify, x=juzicode.com
b'juzicode.com\x00no'
指针: 修改字符串,c_char_p
modify_c2: before modify, x=ABCDEFGHIJKLMNO
modify_c2: after  modify, x=juzicode.com
b'juzicode.com\x00NO'

5.5 字符数组

下面的例子中入参为字符数组,这个C函数实现在传入的字符数组前加入一段字符串的功能。这个C函数传递的是字符数组,调用者可以通过length来保证指针不会越界:

__declspec(dllexport) int modify_c_array(char x[],int length)
{
	printf("modify_c_array: before modify, x=%s\n", x);
	char *temp = new char[length];
	memcpy(temp,x, length);
	memset(x, 0, length);
	if (length < 50) { //检查长度
		return -1;
	}
	strcpy(x, "juzicode.com ");
	strcat(x, temp);
	delete temp;
	printf("modify_c_array: after  modify, x=%s \n", x);
	return 0;
}

在Python中可以用c_char*100(根据字符数组长度确定)、POINTER(c_char)、c_char_p 等3种形式的入参声明。


print()
print('指针: 字符数组转换,c_char_100')
c_char_100 = c_char * 100
x = c_char_100() 
x.value = b'abcde' 
pyt.modify_c_array.restype=c_int   
pyt.modify_c_array.argtypes=(c_char_100,c_int) #c_char_100方式声明
pyt.modify_c_array(x,100) 
print(x.value)

print('指针: 字符数组转换,POINTER(c_char)')
x.value = b'ABCDE'
pyt.modify_c_array.restype=c_int   
pyt.modify_c_array.argtypes=(POINTER(c_char),c_int) #POINTER(c_char)方式声明
pyt.modify_c_array(x,100)    
print(x.value)

print('指针: 字符数组转换,c_char_p')
x.value = b'XYZ'
pyt.modify_c_array.restype=c_int  
pyt.modify_c_array.argtypes=( c_char_p ,c_int)  #c_char_p 方式声明
pyt.modify_c_array(x,100)    
print(x.value)
==========结果:

指针: 字符数组转换,c_char_100
modify_c_array: before modify, x=abcde
modify_c_array: after  modify, x=juzicode.com abcde
b'juzicode.com abcde'
指针: 字符数组转换,POINTER(c_char)
modify_c_array: before modify, x=ABCDE
modify_c_array: after  modify, x=juzicode.com ABCDE
b'juzicode.com ABCDE'
指针: 字符数组转换,c_char_p
modify_c_array: before modify, x=XYZ
modify_c_array: after  modify, x=juzicode.com XYZ
b'juzicode.com XYZ'

5.6 结构体指针

结构体指针和其他数据类型指针也一样,在声明函数入参或返回类型的时候用POINTER(类型),在实际调用函数的使用用pointer(变量)的方式。下面这个例子在C函数中修改结构体的成员变量的值:

typedef struct __ts_fruit {
	int id;
	char name[10];
	float weight;
}TS_FRUIT, *PTS_FRUIT;

__declspec(dllexport) int modify_struct_p(PTS_FRUIT fruit)
{
	fruit->id = 202008;
	memset(fruit->name, 0, 10);
	strcpy(fruit->name, "juzicode");
	fruit->weight = 11.5;
	return 0;
}
class TsFruit(Structure):          #定义ctypes类型的“结构体”
    _fields_ = [('id', c_int), 
	            ('name', c_char*10),
	            ('weight', c_float),				
	            ]
		
pyt = CDLL('pytest.dll') #加载dll
fruit = TsFruit(10001,b'juzi',50) #初始化

pyt.modify_struct_p.restype=c_int    #声明返回参数类型和入参类型
pyt.modify_struct_p.argtypes=(POINTER(TsFruit),)

pyt.modify_struct_p(pointer(fruit))           #执行
 
print('id:',fruit.id)             #使用成员变量
print('name:',fruit.name)
print('weight:',fruit.weight)
 
==========结果:
id: 202008
name: b'juzicode'
weight: 11.5

5.7 void*指针

ctypes中定义的c_void_p对应C语言的void*指针,在Python中可以使用cast方法进行指针类型转换,下面的例子中入参是一个void*指针类型,在C函数内部进行类型转换并根据转换后的类型打印其内容。在Python中使用 新变量= cast(pointer(待转换变量),c_void_p)方法进行转换后传入到C函数中,这个新变量就是转换为c_void_p后的数据类型。

//void* 指针
__declspec(dllexport) int void_pointer(void* x)
{
	char* cp = (char*)(x);
	printf("void_pointer: thransfer to char, x=%c \n", *cp);

	int* ip = (int*)(x);
	printf("void_pointer: thransfer to int, x=%d \n", *ip);

	return 0;
}
from ctypes import *
 
pyt = CDLL('pytest.dll')              #加载dll
pyt.void_pointer.restype=c_int        #声明返回参数类型和入参类型
pyt.void_pointer.argtypes=(c_void_p,)

a = c_char(b'j')                      #字母'j'
b = cast(pointer(a),c_void_p)         #转换为c_void_p
pyt.void_pointer(b)                   #执行

a = c_int(117)                        #字母'u'的ascii值
b = cast(pointer(a),c_void_p)         #转换为c_void_p
pyt.void_pointer(b)                   #执行
==========结果:
void_pointer: thransfer to char, x=j
void_pointer: thransfer to int, x=106

void_pointer: thransfer to char, x=u
void_pointer: thransfer to int, x=117

ctypes 指针小结:1.函数入参或返回值声明使用POINTER(类型),函数调用使用pointer(实例/变量)的方式。2.void*类型使用cast()方法进行类型转换后传入C函数。

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注