目录
一、ctypes介绍
ctypes 是 Python 的外部函数库。它提供了与 C 兼容的数据类型,并允许调用 DLL 或共享库中的函数。可使用该模块以纯 Python 形式对这些库进行封装。
我们在使用python进行开发的时候,时常受限于python的运行效率,比如在进行密集计算或者密集的io操作时候。此时,我们就可以使用python的内置函数库ctypes,它可以相对轻松通过调用dll(so)的形式将c/c++的函数嵌入到python程序中,以此提高python瓶颈处的运行效率。
二、开发环境配置
在众多的python开发环境中,我个人最喜欢的是轻量级的sublime而不是笨重的vscode或者pycharm。vscode虽然饱受好评和赞誉,但是我自己还是喜欢体积很小并且秒开的sublime。至于pycharm,他对于开发大型的项目还是非常不错的,但因为他只能作为python的IDE,所以在这里我旧直接排除掉他了。sunlime是非常方便在多种语言直接快速切换,所以这首推sublime.
下面我就介绍sublime开发环境的配置,我之前已经写了一篇关于python开发环境的配置,一篇关于Mingw编译器的配置。所以在这里我就不再展开,大家可以点击链接查看学习
三、ctypesd 的简单入门
(以下内容 大部分粘贴自官方Documentation )
-
载入动态连接库
ctype导出了 cdll 对象,在 Windows 系统中还导出了 windll 和 oledll 对象用于载入动态连接库。您可以通过访问这些对象的属性来加载库。 cdll 加载使用标准 cdecl
调用约定导出函数的库,而 windll 库则使用 stdcall
调用约定调用函数。 oledll 也使用 stdcall
调用约定,并假定函数返回 Windows HRESULT
错误代码。 当函数调用失败时会使用错误代码自动引发 OSError 异常。
from ctypes import *
import ctypes
lib = ctypes.cdll.LoadLibrary("msvcrt.dll")
print(lib)
>><CDLL 'msvcrt.dll', handle 7ffb26cd0000 at 0x1bc2a4cdc48>
#msvcrt为是微软 C 标准库,包含了大部分 C 标准函数,这些函数都是以 cdecl 调用协议进行调用的。
#LoadLibrary为调用dll功能的函数
#Windows 会自动添加通常的 .dll 文件扩展名。
-
操作导入的动态链接库中的函数
我们先写一段比较简单的c代码,然后通过mingw编译生成dll,python程序通过操作dll对象的属性来操作这些函数。
example1.c
//example1.c
#include <stdio.h>
int hello() {
printf("hello!\n");
return 0;
}
extern "C"{
int _hello_()
{ return hello();}
}
//如果你设置编译器时,使用了g++,则需要使用extern "C"{}进行声明,因为ctypes仅支持C的类型,用这个方法这可以式ctypes也能调用c++的函数。
example1.py
from ctypes import *
import ctypes
lib = ctypes.cdll.LoadLibrary("example1.dll")
lib._hello_()
#运行结果
#>>hello!
我们使用sublime配置好的c++ for dll 编译环境编译example1.c,使其生成example1.dll动态链接库。然后我们切换sublime的python开发环境,运行example1.py。我们将得到输出结果Hello!
四、基础数据及函数介绍
(以下内容 大部分粘贴自官方Documentation )
-
基础数据类型
ctypes 定义了一些和C兼容的基本数据类型:
-
构造函数接受任何具有真值的对象。
所有这些类型都可以通过使用正确类型和值的可选初始值调用它们来创建:
>>>c_int()
c_long(0)
#注意c_long(0)和c_int()在这里都属于同一种类型,所以这里输出没有问题
>>>c_wchar_p("Hello, World")
c_wchar_p(140018365411392)
>>>c_ushort(-3)
c_ushort(65533)
这些类型是可变的,它们的值也可以在以后更改:
i = c_int(42)
print(i)
>>>c_long(42)
print(i.value)
>>>42
i.value = -99
print(i.value)
>>>-99
当给指针类型的对象 c_char_p, c_wchar_p 和 c_void_p 等赋值时,将改变它们所指向的内存地址,而不是 它们所指向的内存区域的内容 (这是理所当然的,因为 Python 的 bytes 对象是不可变的):
s = "Hello, World"
c_s = c_wchar_p(s)
print(c_s)
>>>c_wchar_p(139966785747344)
print(c_s.value)
>>>Hello World
c_s.value = "Hi, there"
print(c_s) # the memory location has changed
>>>c_wchar_p(139966783348904)
print(c_s.value)
>>>Hi, there
print(s) # first object is unchanged
>>>Hello, World
但你要注意不能将它们传递给会改变指针所指内存的函数。如果你需要可改变的内存块,ctypes 提供了 create_string_buffer() 函数,它提供多种方式创建这种内存块。当前的内存块内容可以通过 raw
属性存取,如果你希望将它作为NUL结束的字符串,请使用 value
属性:
from ctypes import *
p = create_string_buffer(3) # create a 3 byte buffer, initialized to NUL bytes
print(sizeof(p), repr(p.raw))
3 b'\x00\x00\x00'
p = create_string_buffer(b"Hello") # create a buffer containing a NUL terminated string
print(sizeof(p), repr(p.raw))
6 b'Hello\x00'
print(repr(p.value))
10 b'Hello\x00\x00\x00\x00\x00'
p = create_string_buffer(b"Hello", 10) # create a 10 byte buffer
print(sizeof(p), repr(p.raw))
10 b'Hi\x00lo\x00\x00\x00\x00\x00'
p.value = b"Hi"
print(sizeof(p), repr(p.raw))
10 b'Hi\x00lo\x00\x00\x00\x00\x00'
-
调用函数
-
一般函数的调用
我们还是以调用msvcrt.dll的例子来说明一般函数的调用方式:from ctypes import * import ctypes lib = ctypes.cdll.LoadLibrary("msvcrt.dll") print(lib) >><CDLL 'msvcrt.dll', handle 7ffb26cd0000 at 0x1bc2a4cdc48> #msvcrt为是微软 C 标准库,包含了大部分 C 标准函数,这些函数都是以 cdecl 调用协议进行调用的。 #LoadLibrary为调用dll功能的函数 #Windows 会自动添加通常的 .dll 文件扩展名。 printf = lib.printf #调用C标准库中的printf函数 printf(b"Hello, %s\n", b"World!") #使用printf打印 Hello, World! 14 #凡是c标准库中的函数都是可以通过这种方法进行调用。
-
指定必须的参数调用函数
在许多平台上通过 ctypes 调用可变函数与调用带有固定数量形参的函数是完全一样的。 在某些平台,特别是针对 Apple 平台的 ARM64 上,可变函数的调用约定与常规函数则是不同的。#通过指定函数的输入类型来使得形参和实参的类型和数量相同 libc.printf.argtypes = [ctypes.c_char_p]
可以通过设置 argtypes 属性来指定从 DLL 导出函数的必选参数类型。
argtypes 必须是一个 C 数据类型的序列(这里
printf()
函数可能不是一个好例子,因为它会根据格式字符串的不同接受可变数量和不同类型的形参,但另一方面这对尝试此功能来说也很方便):printf.argtypes = [c_char_p, c_char_p, c_int, c_double] printf(b"String '%s', Int %d, Double %f\n", b"Hi", 10, 2.2) String 'Hi', Int 10, Double 2.200000 37
example2.c
int AddFun(int a,double a) { return a+b; } extern "C"{ int __AddFun__(){ return AddFun(); } }
example2.py
from ctypes import * import ctypes lib = ctypes.cdll.LoadLibrary("example2.dll") lib.__AddFun__.argtypes=[c_int,cint] print(lib.__AddFun__(25,35) #输出结果60
-
指定返回类型调用函数
ctypes默认情况下都会假定函数返回 C int 类型。 其他返回类型可通过设置函数对象的 restype 属性来指定。
example3.c
examole3.py#include <stdio.h> #include<string> char*fun() { return "3.1415 Hello"; } extern "C"{ char*__fun__(){ return fun(); } }
from ctypes import * import ctypes lib = ctypes.cdll.LoadLibrary("example3.dll") lib.__fun__.restype=[c_char_p] print(lib.__fun__()) #输出结果:b"3.1415 Hello"
-
使用自定义的数据调用函数
你也可以通过自定义 ctypes 参数转换方式来允许将你自己的类实例作为函数参数。 ctypes 会寻找 _as_parameter_ 属性并使用它作为函数参数。 属性必须是整数、字符串、字节串、ctypes 实例或者带有 _as_parameter_ 属性的对象:from ctypes import * import ctypes lib = ctypes.cdll.LoadLibrary("msvcrt.dll") printf = lib.printf #调用C标准库中的printf函数 class Bottles: def __init__(self, number): self._as_parameter_ = number bottles = Bottles(42) printf(b"%d bottles of beer\n", bottles) >>42 bottles of beer
-
指针
-
数组
-
类型转换