ctypes回调:在exi上的访问违规

2024-09-30 00:42:18 发布

您现在位置:Python中文网/ 问答频道 /正文

我以前问过这个问题,没有人回答。我又问了一遍,这次简化了很多。在

我有一个pythonctypes调用的dll,它有一个回调函数。回调在整个过程中都能正常工作(如果我在visualstudio中单步执行程序,我可以看到它正在运行),但是在退出visualstudio时抛出一个“访问冲突”异常。但是如果我从dll中删除对回调的调用,它会正常退出,而不会出现访问冲突。在

我还需要做些什么来退出带有回调的dll吗?我已经研究了好几个小时了,但是我在网上还没有找到任何可以解决这个问题的东西。在

这是ctypes代码。我省略了dll代码来保持这个简短(它是用NASM编写的),但是如果需要的话,我也可以发布它。在

def SimpleTestFunction_asm(X):

    Input_Length_Array = []
    Input_Length_Array.append(len(X)*8)

    CA_X = (ctypes.c_double * len(X))(*X)

    length_array_out = (ctypes.c_double * len(Input_Length_Array))(*Input_Length_Array)

    hDLL = ctypes.WinDLL("C:/Test_Projects/SimpleTestFunction/SimpleTestFunction.dll")
    CallName = hDLL.Main_Entry_fn
    CallName.argtypes = [ctypes.POINTER(ctypes.c_double),ctypes.POINTER(ctypes.c_double),ctypes.POINTER(ctypes.c_longlong)]
    CallName.restype = ctypes.POINTER(ctypes.c_int64)
    #__________
    #The callback function

    LibraryCB = ctypes.WINFUNCTYPE(ctypes.c_double, ctypes.c_double)

    def LibraryCall(ax):
        bx = math.ceil(ax)
        return (bx)

    lib_call = LibraryCB(LibraryCall)
    lib_call = ctypes.cast(lib_call,ctypes.POINTER(ctypes.c_longlong))

    #__________

    ret_ptr = CallName(CA_X,length_array_out,lib_call)

我真的很感激有什么办法解决这个问题。我希望这个简化的帖子能有所帮助。在

非常感谢。在


Tags: 代码inputlenlibdefcallctypesarray
2条回答

@Mark Tolonen,非常感谢您的详细分析。我将此作为答案发布,因为代码的格式在注释中无法正确显示,但我选择了您的答案作为最佳答案。在

我怀疑堆栈对齐可能是问题所在,并且您消除了ctypes作为源代码,所以我将重点放在堆栈上。这是我所做的。在

在NASM代码中,我在进入时按rbp和rdi,然后在退出时恢复它们。在这里,在调用之前,我通过从堆栈中弹出rbp和rdi来设置堆栈状态。然后从rsp中减去32个字节(不是40个)。调用完成后,我将恢复堆栈状态:

pop rbp
pop rdi
sub rsp,32
call [CB_Pointer] ; The call to the callback function
add rsp,32
push rdi
push rbp

对于一个外部函数调用(比如C库函数),我必须减去40个字节,但是对于这个回调,我只需要32个字节。在你回答之前,我试过用40个字节,但没用。我想原因是因为它不是调用外部库,而是对首先调用dll的ctypes代码的回调。在

还有一件事。调用发送一个浮点值(xmm0)并返回一个整数值,但是整数值返回到xmm0寄存器中,而不是rax。将ctypes中的原型设置为整数返回值并不能做到这一点。它必须保持这样:

^{pr2}$

再次感谢你的答复。你带我去哪里找。在

p.S.length_array_out将输入数组的长度传递给NASM。如果我传递了多个数组,则长度_array_out将更长,每个长度有一个qword;目前我在输入时将qword转换为整数。在

我对您的代码做了一些小的修改,使实际运行(导入),并添加了一个打印来查看传递的对象的地址和返回值,另外还创建了一个等效的C DLL来确保指针正确传递和回调正常工作。在

Python:

import ctypes
import math

def SimpleTestFunction_asm(X):
    Input_Length_Array = []
    Input_Length_Array.append(len(X)*8)

    CA_X = (ctypes.c_double * len(X))(*X)

    length_array_out = (ctypes.c_double * len(Input_Length_Array))(*Input_Length_Array)

    hDLL = ctypes.WinDLL('test')
    CallName = hDLL.Main_Entry_fn
    CallName.argtypes = [ctypes.POINTER(ctypes.c_double),ctypes.POINTER(ctypes.c_double),ctypes.POINTER(ctypes.c_longlong)]
    CallName.restype = ctypes.POINTER(ctypes.c_int64)

    LibraryCB = ctypes.WINFUNCTYPE(ctypes.c_double, ctypes.c_double)

    def LibraryCall(ax):
        bx = math.ceil(ax)
        return (bx)

    lib_call = LibraryCB(LibraryCall)
    lib_call = ctypes.cast(lib_call,ctypes.POINTER(ctypes.c_longlong))

    ret_ptr = CallName(CA_X,length_array_out,lib_call)
    print('{:016X} {:016X} {:016X} {}'.format(ctypes.addressof(CA_X),ctypes.addressof(length_array_out),ctypes.addressof(lib_call.contents),ret_ptr.contents))

SimpleTestFunction_asm([1.1,2.2,3.3])

在测试.DLL资料来源:

^{pr2}$

输出:

0000021CC99B23A8 0000021CCBADAC10 0000021CCBC90FC0 2.000000
0000021CC99B23A8 0000021CCBADAC10 0000021CCBC90FC0 c_longlong(123)

您可以看到指针是相同的,回调返回值和函数返回值是正确的。在

很可能您的NASM代码没有正确实现调用约定,或者破坏了访问数组的堆栈。我只是做了最起码的工作来让你的Python代码正常工作。我确实觉得奇怪,length_array_out总是一个长度为1的双数组,其值是输入数组X长度的8倍。NASM代码如何知道数组的长度?在

{and可以声明一个回调函数来代替cd3}类型转换:

CALLBACK = ctypes.WINFUNCTYPE(ctypes.c_double, ctypes.c_double)

CallName.argtypes = [ctypes.POINTER(ctypes.c_double),ctypes.POINTER(ctypes.c_double),CALLBACK]
CallName.restype = ctypes.POINTER(ctypes.c_int64)


@CALLBACK
def LibraryCall(ax):
    bx = math.ceil(ax)
    return (bx)

ret_ptr = CallName(CA_X,length_array_out,LibraryCall)

相关问题 更多 >

    热门问题