之前我没有回复过这个问题。我再次问它,这次更加简化了。
我有一个由Python ctypes调用的dll,带有一个回调函数。回调一直正常工作(如果我在Visual Studio中逐步执行该程序,我可以在操作中看到它),但在退出时,Visual Studio会抛出“访问冲突”异常。但是如果我从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)
我真的非常感谢有关如何解决这个问题的任何想法。我希望这篇简化的帖子会有所帮助。
非常感谢。
答案 0 :(得分:3)
我对你的代码进行了一些小改动,以实际运行(导入)并添加了一个打印,以查看传递的对象的地址和返回值,并创建了一个等效的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;
}
输出:
0000021CC99B23A8 0000021CCBADAC10 0000021CCBC90FC0 2.000000
0000021CC99B23A8 0000021CCBADAC10 0000021CCBC90FC0 c_longlong(123)
您可以看到指针是相同的,并且回调返回值和函数返回值是正确的。
您的NASM代码可能没有正确实现调用约定或破坏访问数组的堆栈。我只是尽力使你的Python代码工作。我确实认为length_array_out
始终是长度为1的双数组,其值为输入数组X
长度的8倍。 NASM代码如何知道数组有多长?
您可以更加确定类型并声明以下内容,而不是将回调转换为long 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)
答案 1 :(得分:0)
@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个字节并且它不起作用。我想原因是因为它没有调用外部库,它是对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转换为整数。