You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在不保存到磁盘的情况下编译并加载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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 12:15:18