關於Qt動態載入dll時,函式指標取地址OK,使用時卻有問題
碰到如題所述的問題,很尷尬,有些庫函式可以正常使用,而有些一用就game over。還以為是庫有問題。
呼叫介面函式時,崩掉???
根據網上同仁給出的解決方法,是在定義函式指標型別時新增一個_stdcall。但也存在一個缺陷,只能在Windows平臺上幫助解決問題。如下所示
typedef _stdcall int (*ABOUT)();
那麼問題來了,為什麼加_stdcall修飾的函式,就可以呢?
這裡涉及引數入棧問題。
從頭來:假設一個函式: int fun(int a, int b);
當我們呼叫這個函式,如在main函式有如此呼叫 int res = fun(10,12)
對於引數的傳遞來說,計算機提供了一個被稱為棧的資料結構來支援引數的傳遞。棧是一個先進後出的資料結構,和彈夾類似,所以有壓棧的說法。
棧有一個棧區(儲存區)、一個棧頂指標。棧頂指標指向堆疊中第一個可用的資料項(稱為棧頂)。我們可以在棧頂 上方向棧中加入資料,這個操作稱為壓棧(Push)。壓棧後棧頂自動變成新加入的資料項,同時棧頂指標也指向新加入專案的地址。 相反的操作稱為出棧(Pop)。
在函式呼叫時,呼叫者依次把引數壓棧,然後呼叫函式,函式被呼叫後,在從堆疊中取得資料,並進行計算。
在函式呼叫結束時,或者由呼叫者或者由函式本身修改棧,是堆疊恢復原裝。-----清理堆疊
在引數傳遞過程中,有兩個很重要的問題必須明確說明:
1、當引數個數多於一個時,按照什麼順序把引數壓入堆疊;——引數傳遞順序
2、函式呼叫結束後,由誰來把堆疊恢復原狀。——棧的維護者
因此,在高階語言中,通過函式呼叫約定來解決這兩個問題。
何謂函式呼叫約定:描述引數是如何傳遞和由誰平衡堆疊的,以及返回值。
常見的函式呼叫約定有:
__stdcall __cdcel __fastcall thiscall
關鍵字 棧的維護者(平衡) 引數傳遞順序
__cdcel 呼叫者 引數反序入棧(右-->左)
__stdcall 被呼叫函式 引數反序入棧(右-->左)
__fastcall 被呼叫函式 引數先存暫存器,接著入棧
thiscall(非關鍵字) 被呼叫函式 引數入棧,this指標存ECX
__stdcall呼叫約定更多的時候稱為pascal呼叫約定,這是因為pascal是早期很常見的一種教學用計算機程式設計語言,其語法嚴謹,使用的函式呼叫約定就是__stdcall.
在Microsft C++系列的C/C++編譯器中,常常用PASCAL巨集來宣告__stdcall這個呼叫約定,還有WINAPI和CALLBACK巨集亦是。
int __stdcall fun(int a, int b);也就是b先入棧,在a入棧。 由__stdcall產生的名字修飾是在函式名前加下劃線_,並在其後加"@"和函式引數位元組數
本例就是 [email protected] (8表示所需的棧空間)
-----看彙編VC反彙編-----
在main函式 類似於中斷的現場保護
0040108F 6A 03 push 11 //前後兩個操作,可以看出引數是從右向左傳遞,引數也被擴充套件為一個字(4個位元組)。 00401091 6A 02 push 10 00401093 E8 81 FF FF FF call @ILT+20(_Max) (00401019) //這是函式呼叫操作。在進行call操作之後,會自動將call的下一條語句作為 FC mov dword ptr [ebp-4],eax //由暫存器eax帶回返回值 //函式的返回地址儲存在棧中——即地址00401014。
@ILT+0(_Max):
00401015 E9 26 00 00 00 jmp _fun (00401030) 在函式前加_
__cdcel呼叫約定,又稱為C呼叫約定,是C語言的預設的呼叫約定。
int fun(int a, int b); //不加修飾就是C呼叫約定 ==== int __cdcel fun(int a, int b);
C語言中預設引數壓棧順序是從右到左,和__stdcall相同。不同的是,函式本身不清理堆疊,而是有呼叫者來清理堆疊。————————由於這種變化,C呼叫約定允許函式的引數個數是不固定的,這也是C語言的一大特色。
在C++中,可以在函式宣告或定義時用關鍵字__stdcall來指定呼叫約定。
__stdcall呼叫約定經常在Windows程式或API函式中使用。