向NASM 64位DLL传递数组指针后无法正常访问的问题排查
首先,你的核心问题出在Python中传递数组指针的方式错误,导致DLL接收到的不是数组数据缓冲区的地址,而是Python对象本身的指针。让我们逐个拆解你的问题:
1. 为何rcx指向的数组可读但无法写入?
你用ctypes.byref(PVarrNew)传递的是Python中PVarrNew这个ctypes数组对象的指针,而非数组实际数据的起始地址。当你在NASM中读取[rcx+32]时,刚好命中了该Python对象内部的某个可访问字段(可能是缓冲区地址或其他元数据),所以能读取到值;但写入这个位置时,你实际上在修改Python对象的内部结构——这要么触发Python的内存保护(比如写入只读区域),要么破坏对象完整性,导致看似无法写入(或出现未定义行为)。
另外,你在Python中写的PVarrNew[0] = id(PVarrNew)是错误的:id()返回的是Python对象自身的内存地址,不是数组数据缓冲区的地址,这进一步混淆了两个地址的概念。
2. 为何rdx指向的数组既不能读也不能写?
同样,ctypes.byref(arrNew)传递的是arrNew这个Python对象的指针,而非数组数据地址。对于1000个元素的大数组,其Python对象内部结构中,[rdx+32]的位置不属于有效内存区域,或者被Python内存管理器标记为不可访问,因此读写都会触发内存访问错误(只是你的代码没有显式捕获到)。
解决方案
第一步:修正Python的参数传递方式
去掉ctypes.byref(),直接传递数组实例即可——ctypes会自动将其转换为指向数组第一个元素的C指针(符合你声明的c_void_p类型):
# 替换原有的调用代码 retvar = SimpleTest(PVarrNew, arrNew)
如果你需要显式获取指针,也可以用ctypes.addressof()获取缓冲区地址后转换为c_void_p:
retvar = SimpleTest( ctypes.c_void_p(ctypes.addressof(PVarrNew)), ctypes.c_void_p(ctypes.addressof(arrNew)) )
第二步:修正NASM的数组访问逻辑
现在rcx和rdx直接指向数组的第一个元素,访问第n个元素(从0开始)需要计算偏移量:索引 * 8(因为c_int64是8字节)。比如:
[BITS 64] export SimpleTest section .data tempvar: dq 0 section .text finit SimpleTest: push rdi push rbp mov rdi, rcx ; rdi = PVarrNew数组的起始地址 mov rbp, rdx ; rbp = arrNew数组的起始地址 ; 读取PVarrNew[3](偏移3*8=24)到tempvar mov rax, [rdi + 24] mov [tempvar], rax ; 写入PVarrNew[3]为4567 mov rax, 4567 mov [rdi + 24], rax ; 写入arrNew[5]为1235 mov rax, 1235 mov [rbp + 40], rax ; 5*8=40 mov rax, 1235 ; 设置返回值 pop rbp pop rdi ret
第三步:修正Python中的错误赋值
如果你需要在数组中存储缓冲区地址,应该用ctypes.addressof()而非id():
# 替换原有的PVarrNew[0]赋值 PVarrNew[0] = ctypes.addressof(PVarrNew)
验证修正后的完整代码
修正后的Python代码
import ctypes OutputArrayType = ctypes.c_int64 * 1000 arrNew = OutputArrayType() PassArrayType = ctypes.c_int64 * 10 PVarrNew = PassArrayType() # 正确存入数组缓冲区的地址(如果需要) PVarrNew[0] = ctypes.addressof(PVarrNew) PVarrNew[1] = 2 PVarrNew[2] = len(PVarrNew) ThisDll = ctypes.WinDLL(r"C:/Temp2/Std_Math_Formulas.dll") SimpleTest = ThisDll.SimpleTest SimpleTest.argtypes = [ctypes.c_void_p, ctypes.c_void_p] SimpleTest.restype = ctypes.c_int64 # 直接传递数组实例,无需byref retvar = SimpleTest(PVarrNew, arrNew) # 验证结果 print(f"返回值: {retvar}") print(f"PVarrNew[3] 修改后的值: {PVarrNew[3]}") print(f"arrNew[5] 修改后的值: {arrNew[5]}")
编译链接命令保持不变
nasm -Z myfile.err -f Win64 C:\Temp2\Std_Math_Formulas.asm -l myfile.lst -F cv8 -g -o C:\Temp2\Std_Math_Formulas.obj GoLink Std_Math_Formulas.obj /dll /entry SimpleTest msvcrt.dll
这样修改后,你就能正常读写两个数组了。
内容的提问来源于stack exchange,提问作者RTC222

