如何在不保存到磁盘的情况下编译并加载DLL?
直接在内存中加载编译后的DLL(无需写入磁盘)
可以通过两步实现需求:让Clang将编译后的DLL二进制输出到标准输出避免磁盘写入,再调用Windows原生API将二进制数据加载到内存并解析为可执行模块。
完整实现代码
import ctypes import subprocess from ctypes import wintypes # 初始化Windows Kernel32 API kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) # 定义API函数的参数和返回值类型 VirtualAlloc = kernel32.VirtualAlloc VirtualAlloc.argtypes = [wintypes.LPVOID, wintypes.SIZE_T, wintypes.DWORD, wintypes.DWORD] VirtualAlloc.restype = wintypes.LPVOID VirtualProtect = kernel32.VirtualProtect VirtualProtect.argtypes = [wintypes.LPVOID, wintypes.SIZE_T, wintypes.DWORD, wintypes.LPDWORD] VirtualProtect.restype = wintypes.BOOL GetProcAddress = kernel32.GetProcAddress GetProcAddress.argtypes = [wintypes.HMODULE, wintypes.LPCSTR] GetProcAddress.restype = wintypes.LPVOID def load_dll_from_memory(dll_data): # 解析DOS头 dos_header = ctypes.cast(dll_data, ctypes.POINTER(wintypes.IMAGE_DOS_HEADER)) if dos_header.e_magic != 0x5A4D: raise ValueError("无效的DOS头") # 解析NT头 nt_header_addr = ctypes.addressof(dos_header.contents) + dos_header.e_lfanew nt_headers = ctypes.cast(nt_header_addr, ctypes.POINTER(wintypes.IMAGE_NT_HEADERS64)) if nt_headers.Signature != 0x4550: raise ValueError("无效的NT头") # 分配足够的内存空间 image_base = VirtualAlloc( None, nt_headers.OptionalHeader.SizeOfImage, 0x1000 | 0x2000, # MEM_COMMIT | MEM_RESERVE 0x40 # PAGE_EXECUTE_READWRITE ) if not image_base: raise ctypes.WinError(ctypes.get_last_error()) # 复制PE头到内存 ctypes.memmove(image_base, dll_data, nt_headers.OptionalHeader.SizeOfHeaders) # 复制各个节数据 section_headers = ctypes.cast( nt_header_addr + ctypes.sizeof(nt_headers.contents), ctypes.POINTER(wintypes.IMAGE_SECTION_HEADER) ) for i in range(nt_headers.FileHeader.NumberOfSections): section = section_headers[i] dest_ptr = ctypes.c_void_p(image_base) + section.VirtualAddress src_ptr = ctypes.c_char_p(dll_data) + section.PointerToRawData ctypes.memmove(dest_ptr, src_ptr, section.SizeOfRawData) # 处理基地址重定位 reloc_dir = nt_headers.OptionalHeader.DataDirectory[5] if reloc_dir.Size > 0: reloc_table = ctypes.cast( ctypes.c_void_p(image_base) + reloc_dir.VirtualAddress, ctypes.POINTER(wintypes.IMAGE_BASE_RELOCATION) ) while reloc_table.VirtualAddress != 0: entry_count = (reloc_table.SizeOfBlock - ctypes.sizeof(reloc_table.contents)) // 2 entries = ctypes.cast( ctypes.addressof(reloc_table.contents) + ctypes.sizeof(reloc_table.contents), ctypes.POINTER(wintypes.WORD) ) for j in range(entry_count): entry = entries[j] reloc_type = entry >> 12 offset = entry & 0xFFF if reloc_type == 3: # 处理64位地址重定位 addr_ptr = ctypes.cast( ctypes.c_void_p(image_base) + reloc_table.VirtualAddress + offset, ctypes.POINTER(wintypes.ULONGLONG) ) addr_ptr.contents.value += ctypes.cast(image_base, wintypes.LONG) - nt_headers.OptionalHeader.ImageBase # 移动到下一个重定位块 reloc_table = ctypes.cast( ctypes.addressof(reloc_table.contents) + reloc_table.SizeOfBlock, ctypes.POINTER(wintypes.IMAGE_BASE_RELOCATION) ) # 调用DllMain完成初始化 dll_main = ctypes.cast( GetProcAddress(image_base, b"DllMain"), ctypes.CFUNCTYPE(wintypes.BOOL, wintypes.HMODULE, wintypes.DWORD, wintypes.LPVOID) ) if not dll_main(image_base, 0x1, None): # DLL_PROCESS_ATTACH raise RuntimeError("DllMain初始化失败") return image_base # 1. 编译C代码到二进制数据(输出到stdout) source_code = ''' #include <stdio.h> __declspec(dllexport) int add(int a, int b) { return a + b; } ''' # 调用Clang编译,-o -表示输出到标准输出 dll_binary = subprocess.check_output( args=['clang', '-shared', '-x', 'c', '-', '-o', '-', '-target', 'x86_64-windows'], input=source_code.encode('utf-8') ) # 2. 加载内存中的DLL module_handle = load_dll_from_memory(dll_binary) # 获取导出函数并调用 add_func = ctypes.cast( GetProcAddress(module_handle, b"add"), ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_int) ) print(add_func(1, 2)) # 输出3 # 可选:卸载模块 # kernel32.FreeLibrary(module_handle)
注意事项
- 上述代码针对Windows x64架构,若使用32位系统,需将
IMAGE_NT_HEADERS64改为IMAGE_NT_HEADERS32,并调整重定位逻辑中的地址类型 - 确保已安装Clang并添加到系统PATH中,编译命令可根据需求调整(比如添加编译参数)
- 内存加载DLL的逻辑手动处理了PE文件的解析、重定位和初始化,是Windows平台下的原生实现,无需依赖第三方库
内容的提问来源于stack exchange,提问作者SzymonO
相关产品推荐
相关产品推荐

