Python 带有回调的ctypes:退出时访问冲突

Python 带有回调的ctypes:退出时访问冲突,python,dll,callback,ctypes,Python,Dll,Callback,Ctypes,我以前问过这个问题,没有回答。我再问一遍,这次要简单得多 我有一个Python ctypes调用的dll,带有回调函数。回调在整个过程中都正常工作(如果我在VisualStudio中单步执行该程序,我可以在操作中看到它),但在退出时,VisualStudio抛出一个“访问冲突”异常。但是,如果我从dll中删除对回调的调用,它将正常退出,而不会出现访问冲突 要使用回调从dll中退出,是否还必须执行其他操作?我已经研究了好几个小时了,但在网上还没有找到解决这个问题的方法 这是ctypes代码。我省略

我以前问过这个问题,没有回答。我再问一遍,这次要简单得多

我有一个Python ctypes调用的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)
我真的非常感谢任何关于如何解决这个问题的想法。我希望这篇简化的文章会有所帮助


非常感谢

我对您的代码做了一些小的更改,使其实际运行(导入),并添加了一个打印,以查看传递的对象的地址和返回值,另外还创建了一个等效的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])
Test.DLL源代码:

#include <inttypes.h>
#include <stdio.h>

typedef double (*CB)(double);

__declspec(dllexport) int64_t* __stdcall Main_Entry_fn(double* p1, double* p2, long long* p3)
{
    static int64_t x = 123;
    double out = ((CB)p3)(1.1);
    printf("%p %p %p %lf\n",p1,p2,p3,out);
    return &x;
}
您可以看到指针是相同的,回调返回值和函数返回值是正确的

您的NASM代码可能没有正确实现调用约定,或者损坏了访问阵列的堆栈。我只是尽了最小的努力让你的Python代码正常工作。我确实觉得奇怪,
length\u array\u out
总是一个长度为1的双数组,其值是输入数组长度的8倍
X
。NASM代码如何知道数组的长度

您可以更准确地声明以下内容,而不是将回调强制转换为
long*

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)

@马克·托洛宁,非常感谢你的详细分析。我将此作为一个答案发布,因为注释中的代码格式不正确——但我选择您的答案作为最佳答案

我怀疑堆栈对齐可能是问题所在,您消除了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个字节试过了,但没有用。我猜原因是因为它没有调用外部库,而是对ctypes代码的回调,它首先调用dll

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

LibraryCB = ctypes.WINFUNCTYPE(ctypes.c_double, ctypes.c_double)
再次感谢您的回复。你告诉我去哪里找


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

一目了然,尝试在
SimpleTestFunction\u asm
之外定义
LibraryCall
。如果
LibraryCall
在调用之前超出范围,可能不太好。@101强制转换创建了对该对象的另一个引用,然后在释放第一个引用时被分配给同一个名称,因此看起来很正常。马克,非常感谢。请看我的答案贴在下面。
LibraryCB = ctypes.WINFUNCTYPE(ctypes.c_double, ctypes.c_double)