[英]ctypes with callback: access violation on exit
我之前沒有回復就問過這個問題。 我再次問它,這次更加簡化了。
我有一個由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)
我真的非常感謝有關如何解決這個問題的任何想法。 我希望這篇簡化的帖子會有所幫助。
非常感謝。
我對您的代碼進行了一些小的更改以實際運行(導入)並添加了一個打印以查看傳遞的對象的地址和返回值,並創建了一個等效的C DLL以確保指針正確傳遞並且回調有效。
蟒蛇:
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)
@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)
再次感謝你的回復。 你告訴我在哪里看。
PS length_array_out將輸入數組的長度傳遞給NASM。 如果我傳遞多個數組,則length_array_out將更長,每個長度只有一個qword; 目前我在輸入時將qword轉換為整數。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.