Python basic library-ctypes

usefulness

ctypes is mainly used to call functions in dll and so

Document translation

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

If the parameters and return values ​​of the function are basic types (strings and numeric values), you can call the function directly without declaration

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!")

If the function parameters or return value contain some complex data types, you need to declare the function parameters (argtypes) and return value (restype) first

from ctypes import *
from ctypes.wintypes import *

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

The return value is different between calling GetModuleHandleA directly and calling it after declaration. This is because the returned pointer type is not interpreted correctly. Therefore, it is best to declare the parameters and types of the function before calling the dll function.

For example, to call GetModuleHandleA of kernel32.dll, you can first search for the function prototype in Microsoft's official documentation.

calling convention

ctypes only supports two calling conventions, cdll supports the cdecl calling convention, and windll supports the stdcall calling convention. There is also oledll which also has stdcall calling convention. I don’t see any difference from windll.

type of data

Insert image description here

Using ctypes types
from ctypes import *

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

i.value = 100
print(i)
Pass pointer

You can use the byref function to pass the pointer. Of course, you can also use the pointer function, which has the same effect, but byref is more efficient because pointer needs to construct a real 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

Inherit Structure and then declare the _fields_ field to define a structure type

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)

Structure nesting

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)
Structure field alignment and endianness

The default alignment is the same as C. It can be defined using the _pack_ attribute. The value can be set to a positive integer, indicating the maximum alignment of the field. The #pragma pack(n)effect is the same.

Structures in ctypes use local byte order. To use non-native byte order, you can use BigEndianStructure, LittleEndianStructure, BigEndianUnion, LittleEndianUnion as base classes. These classes cannot contain pointer fields

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

Equivalent to

char a[4] = "abc";
Type cast

For example, casting a float type pointer to an int type pointer

from ctypes import *

a = pointer(c_float(3.14))

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

Of course the output result is definitely not 3

Callback

Define a function in Python that can be called in the dll

qsort is a sorting function. The first parameter is the sorted array, the second is the length of the array, the third is the size of the array elements, and the fourth is a callback function. If the return value is less than 0, a will be placed in front of b. , if greater than 0, a will be placed after 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 defines the cdecl calling function, and WINFUNCTYPE defines the stdcall calling function. The first parameter is the return value type, and the following is the parameter type.

It can also be defined in the form of a decorator

@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 exported value

Dynamic link libraries can not only export functions, but also export variables. The pythonapi here is actually the loaded python.dll

from ctypes import *

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

example

Enumerate all module information of the process

C language approximate code

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 translation

Step 1: Define the structure 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), # 模块路径
    ]

Step 2: Define the function

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,))

third step:

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()))

Guess you like

Origin blog.csdn.net/Qwertyuiop2016/article/details/125841653