ctypes主要还是用于调用dll和so里面的函数
from ctypes import windll, cdll
# 加载Windows的kernel32.dll
print(windll.kernel32)
# 加载Windows的msvcrt.dll,和libc一样,里面是Windows的标准库函数
print(cdll.msvcrt)
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有啥区别
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]
动态链接库不止可以导出函数,也可以导出变量。这里的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()))