Python基础库-ctypes

目录

    • 用处
    • 文档翻译
        • windows加载dll
        • linux加载so
        • 声明函数
        • 调用约定
        • 数据类型
        • 使用ctypes类型
        • 传递指针
        • 结构体
        • 结构体 字段对齐和字节序
        • 数组
        • 类型强制转换
        • 回调函数
        • dll导出的值
    • 例子

用处

ctypes主要还是用于调用dll和so里面的函数

文档翻译

windows加载dll
from ctypes import windll, cdll
# 加载Windows的kernel32.dll
print(windll.kernel32)
# 加载Windows的msvcrt.dll,和libc一样,里面是Windows的标准库函数
print(cdll.msvcrt)
linux加载so
from ctypes import cdll, CDLL
# 方法1
libc1 = cdll.LoadLibrary("libc.so.6")
# 方法2
libc2 = CDLL("libc.so.6")
声明函数

如果函数的参数和返回值是基础类型(字符串和数值),则可以直接调用函数,无需声明

from ctypes import windll
handle = windll.kernel32.GetModuleHandleA(None)
print(hex(handle ))
t = cdll.msvcrt.time(None)
print(t)
printf = cdll.msvcrt.printf
printf(b"Hello, %s\n", b"World!")

如果函数参数或返回值包含一些复杂数据类型,则需要先声明函数参数(argtypes )和返回值(restype)

from ctypes import *
from ctypes.wintypes import *

GetModuleHandleA = windll.kernel32.GetModuleHandleA
GetModuleHandleA.argtypes = (LPCSTR,)
GetModuleHandleA.restype = HMODULE
handle = GetModuleHandleA(None)
print(hex(handle))

直接调用GetModuleHandleA和声明之后调用,返回值是不一样的,这是因为返回的指针类型没有被正确解释。所以调用dll的函数前最好先声明函数的参数和类型

比如调用kernel32.dll的GetModuleHandleA,可以先在微软的官方文档中搜一下函数原型

调用约定

ctypes只支持两种调用约定,cdll支持cdecl调用约定,windll支持stdcall调用约定。还有个oledll也是stdcall调用约定,没看到和windll有啥区别

数据类型

Python基础库-ctypes_第1张图片

使用ctypes类型
from ctypes import *

i = c_int(10)
print(i)
print(c_wchar_p("Hello, World"))
print(c_ushort(-3))

i.value = 100
print(i)
传递指针

可以用byref函数来传递指针,当然也可以用pointer函数,一样的效果,但是byref效率更高,因为pointer需要构造一个真实的指针。

from ctypes import *

i = c_int(100)
f = c_float(3.14)
s = create_string_buffer(b"address: ")
cdll.msvcrt.printf(b"%s %p %x", s, byref(f), pointer(i))
结构体

继承Structure,然后声明_fields_ 字段就可以定义一个结构体类型

from ctypes import *

class POINT(Structure):
    _fields_ = [("x", c_int),
                ("y", c_int)]


point = POINT(10, 20)
print(point.x, point.y)
point = POINT(y=5)
print(point.x, point.y)

结构体嵌套

class RECT(Structure):
	_fields_ = [("upperleft", POINT),
				("lowerright", POINT)]
rc = RECT(POINT(1, 2), POINT(3, 4))
# rc = RECT((1, 2), (3, 4))
print(rc.upperleft.x, rc.upperleft.y)
print(rc.lowerright.x, rc.lowerright.y)
结构体 字段对齐和字节序

默认对齐方式和C一样。可以用_pack_ 属性来定义,值可以设置一个正整数,表示字段的最大对齐方式,和#pragma pack(n)效果是一样的

ctypes 中的结构体使用的是本地字节序,要使用非本地字节序,可以使用 BigEndianStructure, LittleEndianStructure, BigEndianUnion, LittleEndianUnion 作为基类。这些类不能包含指针字段

数组
# 定义
a = c_char * 4
# 赋值
s = a(b'a', b'b', b'c', b'\x00')

等同于

char a[4] = "abc";
类型强制转换

比如将一个float类型指针强制转换为int类型指针

from ctypes import *

a = pointer(c_float(3.14))

print(cast(a, POINTER(c_int)).contents)

当然输出结果肯定不是3

回调函数

在Python中定义一个可以在dll里面被调用的函数

qsort是一个排序函数,第一个参数是排序的数组,第二个是数组长度,第三个是数组元素的大小,第四个是个回调函数,如果返回值小于0,a将放在b前面,如果大于0,a将放在b后面

from ctypes import *


def py_cmp_func(a, b):
    print("py_cmp_func", a[0], b[0])
    return a[0]-b[0]


IntArray5 = c_int * 5
ia = IntArray5(5, 1, 7, 33, 99)
qsort = cdll.msvcrt.qsort
qsort.restype = None

CMPFUNC = CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))
cmp_func = CMPFUNC(py_cmp_func)

qsort(ia, len(ia), sizeof(c_int), cmp_func)  
print(list(ia))

CFUNCTYPE定义cdecl调用约定的函数,WINFUNCTYPE定义stdcall 调用约定的函数。第一个参数为返回值类型,后面的则是参数类型

也可以通过装饰器的形式定义

@CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))
def py_cmp_func(a, b):
    print("py_cmp_func", a[0], b[0])
    return a[0] - b[0]
dll导出的值

动态链接库不止可以导出函数,也可以导出变量。这里的pythonapi,其实就是加载的python.dll

from ctypes import *

opt_flag = c_int.in_dll(pythonapi, "Py_OptimizeFlag")
print(opt_flag)

例子

枚举进程的所有模块信息

c语言大概代码

hModuleSnap = CreateToolhelp32Snapshot( TH32CS_SNAPMODULE32 | TH32CS_SNAPMODULE, dwPID ); 
if( hModuleSnap == INVALID_HANDLE_VALUE ) { 
   return( r_mi ); 
} 
me32.dwSize = sizeof( MODULEENTRY32 ); 
if( !Module32First( hModuleSnap, &me32 ) ) { 
   CloseHandle( hModuleSnap );
   return( r_mi ); 
} 
do { 
   
} while( Module32Next( hModuleSnap, &me32 ) );

Python翻译

第一步:定义结构体 MODULEENTRY32

from ctypes import *
from ctypes.wintypes import *

class MODULEENTRY32(Structure):
    _fields_ = [
        ("dwSize", DWORD), # 结构的大小,以字节为单位,必须先初始化
        ("th32ModuleID", DWORD), # 该成员不再使用,并且始终设置为 1
        ("th32ProcessID", DWORD), # 进程pid
        ("GlblcntUsage", DWORD), # 无意义, 一般等于0xFFFF
        ("ProccntUsage", DWORD), # 无意义, 一般等于0xFFFF
        ("modBaseAddr", POINTER(BYTE)), # 拥有进程上下文中模块的基地址
        ("modBaseSize", DWORD), # 模块的大小,以字节为单位
        ("hModule", HMODULE), # 拥有进程上下文中的模块句柄
        ("szModule", c_char*256), # 模块名称
        ("szExePath",  c_char*260), # 模块路径
    ]

第二步:定义函数

kernel32 = WinDLL('kernel32', use_last_error=True)

def func_def(name, restype, *argtypes, dll=kernel32):
    def errcheck(result, func, args):
        if not result:
            raise WinError(get_last_error())
        return result
    cfunc = getattr(dll, name)
    cfunc.argtypes = argtypes
    cfunc.restype = restype
    #cfunc.errcheck = errcheck
    return cfunc
    
CreateToolhelp32Snapshot = func_def("CreateToolhelp32Snapshot", HANDLE, *(DWORD, DWORD))
Module32First = func_def("Module32First", BOOL, *(HANDLE, POINTER(MODULEENTRY32)))
Module32Next = func_def("Module32Next", BOOL, *(HANDLE, POINTER(MODULEENTRY32)))
CloseHandle = func_def("CloseHandle", BOOL, *(HANDLE,))

第三步:

TH32CS_SNAPMODULE = 0x00000008
TH32CS_SNAPMODULE32 = 0x00000010

def getModuleInfo(moduleName, pid):
    '''获取模块信息,返回模块信息的字典'''
    hModuleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE|TH32CS_SNAPMODULE32, pid)

    me32 = MODULEENTRY32()
    me32.dwSize = sizeof(MODULEENTRY32)
    
    bRet = Module32First(hModuleSnap, pointer(me32))
    while bRet:
        szModule = me32.szModule.decode()
        if szModule.upper() == moduleName.upper():
            addr = cast(me32.modBaseAddr, c_void_p).value # hex(addressof(modBaseAddr.contents))
            CloseHandle(hModuleSnap)
            try:
                me32.szExePath.decode("gbk")
            except UnicodeDecodeError:
                print(me32.szExePath)
            module = {
                'modBaseSize': me32.modBaseSize, # 模块字节大小
                'th32ProcessID': me32.th32ProcessID, # 进程pid
                'modBaseAddr': addr, # 模块基址
                "hModule": me32.hModule, # 模块句柄
                'szModule': me32.szModule.decode("ansi"), # 模块名称
                'szExePath': me32.szExePath.decode("ansi") # 模块路径
            }
            return module
        bRet = Module32Next(hModuleSnap, pointer(me32) )
    CloseHandle(hModuleSnap)

import os
import sys

py_version = str(sys.version_info[0]) + str(sys.version_info[1])
print(getModuleInfo(f"python{py_version}.dll", os.getpid()))

你可能感兴趣的:(Python基础,python,ctypes)