如何从NASM DLL返回指针数组?ctypes调用返回值异常问题
解决ctypes调用NASM DLL返回指针数组的问题
看起来你遇到的核心问题是ctypes默认返回值类型不匹配,加上x86-64调用约定中无法直接返回数组导致的。单独返回指针时,你可能隐式处理了返回类型,或者刚好指针的低32位没被截断,但返回数组时,ctypes默认把返回值当成32位int,直接截断了64位指针的高位,所以拿到的是极小的数值。下面是具体的解决思路和代码示例:
问题根源拆解
- 返回值类型未显式声明:ctypes默认返回
c_int(32位),但64位指针是8字节,直接截断后就会变成极小的数值,这是你踩坑的核心原因。 - x86-64无法直接返回数组:函数不能直接返回数组对象,必须返回指向数组的指针——不管这个数组是存在DLL的
.data静态段,还是在堆上动态分配的。
解决方案1:使用静态.data段的指针数组(简单但非线程安全)
NASM DLL代码(以Windows x86-64为例)
section .data ; 预定义存储三个指针的数组(每个指针8字节,共24字节) Return_Pointer_Array: dq 0, 0, 0 section .text extern malloc ; 链接C标准库的malloc global MyFunction MyFunction: ; 分配第一个数组(示例分配10个int,共40字节) mov rcx, 40 call malloc mov [Return_Pointer_Array], rax ; 保存第一个指针 ; 分配第二个数组 mov rcx, 40 call malloc mov [Return_Pointer_Array + 8], rax ; 偏移8字节存第二个指针 ; 分配第三个数组 mov rcx, 40 call malloc mov [Return_Pointer_Array + 16], rax ; 偏移16字节存第三个指针 ; 返回指向指针数组的地址(关键:返回指针,不是数组本身) lea rax, [Return_Pointer_Array] ret
Python ctypes代码
import ctypes # 加载DLL:Windows下如果是stdcall调用约定用WinDLL,cdecl用CDLL my_dll = ctypes.CDLL("your_dll_name.dll") # 方法1:用结构体描述指针数组 class PointerTriplet(ctypes.Structure): _fields_ = [ ("final1_ptr", ctypes.c_void_p), ("final2_ptr", ctypes.c_void_p), ("final3_ptr", ctypes.c_void_p) ] # 声明函数返回类型为指向该结构体的指针 my_dll.MyFunction.restype = ctypes.POINTER(PointerTriplet) # 调用并获取结果 result_ptr = my_dll.MyFunction() result = result_ptr.contents print(f"Final1: {hex(result.final1_ptr)}") print(f"Final2: {hex(result.final2_ptr)}") print(f"Final3: {hex(result.final3_ptr)}") # 方法2:直接用指针数组类型 my_dll.MyFunction.restype = ctypes.POINTER(ctypes.c_void_p * 3) result_array = my_dll.MyFunction().contents print(f"Final1: {hex(result_array[0])}") print(f"Final2: {hex(result_array[1])}") print(f"Final3: {hex(result_array[2])}")
解决方案2:堆上动态分配指针数组(线程安全,推荐)
静态.data段的数组是全局共享的,多次调用函数会覆盖之前的指针。如果需要线程安全或者保留多调用的结果,建议在堆上分配指针数组:
NASM DLL代码
section .text extern malloc, free global MyFunction global FreePointerArray ; 导出释放函数,避免内存泄漏 MyFunction: ; 先分配存储三个指针的数组(3*8=24字节) mov rcx, 24 call malloc mov rbx, rax ; 保存指针数组的地址到rbx ; 分配第一个数组 mov rcx, 40 call malloc mov [rbx], rax ; 分配第二个数组 mov rcx, 40 call malloc mov [rbx + 8], rax ; 分配第三个数组 mov rcx, 40 call malloc mov [rbx + 16], rax ; 返回指针数组的地址 mov rax, rbx ret FreePointerArray: ; 参数:rcx = 指针数组的地址 ; 先释放三个子数组 mov rdx, [rcx] call free mov rdx, [rcx + 8] call free mov rdx, [rcx + 16] call free ; 再释放指针数组本身 call free ret
Python ctypes代码补充
# 声明释放函数的参数类型 my_dll.FreePointerArray.argtypes = [ctypes.c_void_p] my_dll.FreePointerArray.restype = None # 调用完记得释放内存 my_dll.FreePointerArray(result_ptr)
关键注意事项
- 必须显式设置restype:这是解决问题的核心——如果不指定,ctypes默认返回32位int,直接截断64位指针,导致拿到极小值。
- 调用约定匹配:Windows DLL默认是
stdcall,如果NASM里用stdcall约定(比如函数名加@N后缀),就要用ctypes.WinDLL加载;如果是cdecl,用ctypes.CDLL。 - 内存泄漏问题:如果用堆分配,一定要记得释放内存,最好在DLL里导出对应的释放函数,避免跨CRT的内存管理问题。
内容的提问来源于stack exchange,提问作者RTC222
相关产品推荐
相关产品推荐

