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

如何从NASM DLL返回指针数组?ctypes调用返回值异常问题

解决ctypes调用NASM DLL返回指针数组的问题

看起来你遇到的核心问题是ctypes默认返回值类型不匹配,加上x86-64调用约定中无法直接返回数组导致的。单独返回指针时,你可能隐式处理了返回类型,或者刚好指针的低32位没被截断,但返回数组时,ctypes默认把返回值当成32位int,直接截断了64位指针的高位,所以拿到的是极小的数值。下面是具体的解决思路和代码示例:

问题根源拆解

  1. 返回值类型未显式声明:ctypes默认返回c_int(32位),但64位指针是8字节,直接截断后就会变成极小的数值,这是你踩坑的核心原因。
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 09:17:55