1. 程式人生 > >關於Qt動態載入dll時,函式指標取地址OK,使用時卻有問題

關於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函式中使用。