[C++] 優雅玩耍函數指標呼叫,把你同事玩弄得嫑嫑的。(離職前記得回顧這篇文)
前言
最近正好寫一些玩具想模組化,以前在處理 Function Pointer 都是強轉型 + typedef 然後 ^C^V 瘋狂複製貼上函數型別來做到 C/C++ 內對指標函數呼叫。最近正好摸到一些 C++11 有支援到的正規轉型別方式,發現乾...XD 不用在手殘複製貼上啦,原來 C++11 一堆關鍵字已經可以讓你快速定義型別 + 把函數模組化放到自己的樣板內了超ㄎㄧㄤ
btw 這篇只是筆記文,最一開始我只是想查 C/C++ 內到底能不能編譯時期就取得函數的型別XDDDD(結果是:幹,居然真的可以咧XDDDDD)
另外,這篇都在講 Windows 上的做法,不過理論上把 LoadLibrary 跟 GetProcAddress 替換為對應 macOS/Linux 的函數應該都可以 work(N 年前好像有玩一下 macOS 上是可以跑的)
考.考.考.考考古學家
可以先參考這篇 [C++] How to GetProcAddress() like a boss 裡面純 C 寫法的強轉型函數指標做法,這相信常玩函數指標的人都有用過這樣玩法:
原理就只是:
- 宣告一個新的函數型別 ShellAboutProc,型別、呼叫約制跟你想呼叫的系統函數必須一致(對啦,不然等等堆疊爛掉你就知道惹)
- 接著透過 LoadLibrary() 載入系統函數模組取得模組地址、透過 GetProcAddress() 取得該函數位於該函數庫上真正地址
- 以 ShellAboutProc 函數型別宣告一個變數 shellAbout
- 最後將該函數地址強轉型為我們定義好的 ShellAboutProc 型別覆寫入 shellAbout 變數內,大功告成 la,shellAbout 變數就可以被當原生函數呼叫了
這做法超簡單、也實用,不過看也知道一堆地方可以省略XD,比方說根本不需要額外開一個變數來暫存函數指標,可以直接用內聯函數型態的方式、取代宣告函數型別與變數(離題惹)
進階優雅玩法
我寫這篇筆記前有先 survey 了一些文章,有興趣可以參考:Fixing Function Pointers with decltype、Get types of C++ function parameters、 [C++] How to GetProcAddress() like a boss,看完之後你應該會知道大概就是像這樣:
簡單來說:
- typedef decltype(&函數名) 型別名; 可以由 &函數名 refer 到函數型別(純函數名拿到的是函數記憶體地址)再透過 decltype 取出函數宣告型別,然後透過 typedef 將其命名為 a 型別。
- 透過 GetProcAddress() 取得函數地址後,透過 reinterpret_cast<a> 關鍵字可以將括弧中物件轉型為 T 型別,最後這樣回傳回來就是 a 型別的函數宣告好的原生函數,就可以當原生函數呼叫啦
當然上面看起來很醜,你也可以像底下這樣一句話:
(reinterpret_cast(GetProcAddress(LoadLibraryA("User32"), "MessageBoxA")))(0, "hi", "info", 0);
這樣還不夠
如果你跟我一樣非常偷懶,希望把函數物件化來呼叫...XD。參考了一下 [C++] How to GetProcAddress() like a boss,大概就可以寫出這樣的東西:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* @file: brainFuckCppCall.cpp | |
* @author: aaaddress1@chroot.org | |
*/ | |
#include <stdio.h> | |
#include <Windows.h> | |
template<class T> struct func { | |
explicit func(FARPROC ptr) : _ptr(ptr) {} | |
operator T() { return reinterpret_cast<T>(_ptr); } | |
FARPROC _ptr; | |
}; | |
int main(void) { | |
func<decltype(&MessageBoxA)> msgbox(GetProcAddress(LoadLibraryA("user32"), "MessageBoxA")); | |
msgbox(0, "hi", "info", 0); | |
return 0; | |
} |
後續
這種花式玩法主要依賴的兩個關鍵字,可以上微軟爸爸 MSDN 官網上查閱 decltype (C++)、reinterpret_cast Operator。reinterpret_cast 早在 VC2008 之後就支持了、而 decltype 也在 VC2010 也支持了,基本上大部分現在的 IDE 要這樣玩應該都 ok la 我猜
留言
張貼留言