2010年8月31日 星期二

[C++ 文章收集] C++中關於extern "C"的意義


轉載自 這裡
extern "C" 是C++特有的組合關鍵字,在C裡並沒有這個的組合,僅有extern這個關鍵字!
為什麼C++會需要這樣的關鍵字組呢? 原因是C++它有一個複載(overloading)的功能,也就是說同樣的函式名稱可以有多個定義只要參數簽名不同即可。比如說C++裡可以有以下的二個宣告 :
bar(int i, int j);
bar(double i, double j);

這二個函式都是同樣的名字叫foo,僅參數型式不同。然而在C語言裡是不被允許的! C++是如何處理這同名的函式呢? 其實他在編譯時會偷偷的把這二個函式名變成不同的名字,舉例來說bar(int i, int j)可能會被改成_bar_int_int(每種compiler產生不太一樣),而另一個則被改成_bar_double_double。這技術稱Mangling.

問題來了! 當我們希望C++不要偷換函式名時該怎麼辦? 於是就有了extern "C" 這個關鍵字組出現了。這個字組就是請C++不要自己又偷天換日,請它保留原名。所以當我們宣告一個函式如下時 :
extern "C" bar(int i, int j);

編譯器就不會把bar變成_bar_double_double. 實際使用的注意事項 :
1. 當C++使用C的函式庫(library)時,C++不能直接套用C的header檔。因為他會把header裡的宣告給mangleing了。所以他必須使用如下:
  1. extern "C"  
  2. {  
  3. #include "C_LIB.h"  //C_LIB 是C語言所製告出來的。  
  4. }  
2. 相反的,在C語言的編譯器裡若要使用由C++所製告出來的C函式庫,那麼也不能直接的使用C++的header檔。因為此header檔必然存在extern "C" 這個關鍵字組,而這字組C語言是不認識的。所以必需要把C++的header檔裡的extern "C" { } 移除後才可以讓C編譯器使用.

其它有可能因為 extern "C" 造成的問題, 整理如下 :
一組多載函數中, 只能有一個函數被指明為 "extern C", 因為符號修飾後的關係, ex:
  1. void abc(int);  
  2. "extern C" void abc(char);  
  3. "extern C" void abc(float);  
使用 C 方式的修飾符號是 _函數名, 因此是 _abc, 所以當地 2 組指明 "extern C" 也是 _abc 那麼 2組 C 函數的符號就重複了. 此檢查在函數的多載就可以, 但是同一個 scope 中函數沒有多載化就無法檢查出, 更確切的說, "extern "C" 不受 namespace or class 修飾的影響, 因此同一編譯單元中只能有一個同名函數為 "extern C", 否則會有錯誤 ,ex:
  1. namespace N  
  2. {  
  3. extern "C" void abc(){} // _abc  
  4.   
  5. }  
  6.   
  7. extern "C" void abc(){} // _abc  
編譯時產生相同符號 _abc 函數定義故錯誤, 由於 extern "C" 不受限於 namespace 的修飾因此將產生一個有趣現象也就是宣告和定義可以在不同的 namespace 出現. 語法層面上的檢查就如同一般, 而當語法檢查通過後產生符號時, entern "C" 的定義其符號不帶有namespace 的修飾; 同樣的, 呼叫者其符號也不帶有 namespace 的修飾, ex:
  1. namespace Foo  
  2. {  
  3. extern "C" void abc(){} //有定義, 生成符號時, 不受限於 namespace Foo 修飾故符號為 _abc  
  4. }  
  5.   
  6. namespace Bar  
  7. {  
  8. extern "C" void abc(); //純宣告   
  9. }  
  10.   
  11. int main()  
  12. {  
  13. Bar::abc(); //函數呼叫, 生成符號時, 不受限於 namespace Bar 修飾故符號為 _abc, 事實上鏈結到 ::Foo::abc()  
  14. }  
還有下面如此鏈結錯誤的狀況 :
由於extern "C" 沒有多載功能 , 不論是 void abc() 或是 void abc(int a) 都將被以 _abc 來做為函式名稱 , 這是極度危險的.
  1. 1.c  
  2. extern "C" void abc();  
  3. int main()  
  4. {  
  5. abc();  
  6. }  
  7.   
  8. 2.c  
  9. #include  
  10. extern "C" void abc(int a)  
  11. {  
  12. printf("%d",a);  
  13. }  
當 1.c 和 2.c link 時可以 , 因為 c 沒有將參數名稱當作函式修飾名稱的一部分 , 故也就沒有多載 , 將 "C" 拿掉 link 時就會錯誤 ,
因為會以 c++ 的方式來編譯.
This message was edited 1 time. Last update was at 01/09/2010 09:42:26

2010年8月27日 星期五

[C++ 小學堂] 如何建立 export 的 dll 與如何動態呼叫 export 的 dll


前言 :
有關 dll (dynamic link library) 的介紹, 有興趣的可以參考 wiki : Dynamic-link library 的說明. 而它的存在主要是解決許多 常用 的函示被不同的應用程式使用時在記憶體都會占用一分空間造成浪費. 因此藉由 dll 讓所的的應用程式都參考到同一個記憶體位置以節省空間. 接著將介紹如何透過 MS Visual Studio 建立一個 dll 與動態呼叫該 dll export 的函式.

範例代碼 :
1. 步驟一 : 建立 DLL 專案(TestDll) :
首先來建立我們的 dll. 請在 VS 新建專案 > Win32 主控台應用程式 > 下一步 > 接著在應用程式類型選擇 "DLL" 與 其它選項選擇 "空專案" > 點擊完成.

2. 請建立下面代碼並進行編譯建立 Dll 文件 :
- Main.h 代碼 :
  1. extern "C" {  
  2. __declspec(dllexport) double AddNumbers(double a, double b);  
  3. }  

- Main.cpp 代碼 :
  1. #include   
  2. #include "Main.h"  
  3.   
  4. __declspec(dllexport) double AddNumbers(double a, double b) {return a+b;}  

3. 最後會建立 TestDll.dll, 接著再建立一個新的專案(DllLoader), 並建立以下代碼 :
- Main.cpp 代碼 :
  1. #include "Main.h"  
  2. #include   
  3. #include   
  4.   
  5. // DLL function signature  
  6. typedef double (*importFunction)(doubledouble);  
  7.   
  8. int main() {  
  9.     importFunction addNumbers;  
  10.     double result;  
  11.   
  12.     // Load DLL file  
  13.     HINSTANCE hinstLib = LoadLibrary(TEXT("TestDll.dll"));  
  14.     if (hinstLib == NULL) {  
  15.         printf("ERROR: unable to load DLL\n");  
  16.         system("pause");  
  17.         return 1;  
  18.     }  
  19.   
  20.     // Get function pointer  
  21.     addNumbers = (importFunction)GetProcAddress(hinstLib, "AddNumbers");  
  22.     if (addNumbers == NULL) {  
  23.         printf("ERROR: unable to find DLL function\n");  
  24.         FreeLibrary(hinstLib);  
  25.         system("pause");  
  26.         return 1;  
  27.     }  
  28.   
  29.     result = addNumbers(1020);  
  30.     printf("The result is %f\n", result);  
  31.   
  32.     // Unload DLL file  
  33.     FreeLibrary(hinstLib);  
  34.     system("pause");  
  35.     return 0;  
  36. }  

4. 執行後可以得到結果 :
The result is 30.000000

補充說明 :
MSDN > LoadLibrary Function :
Loads the specified module into the address space of the calling process. The specified module may cause other modules to be loaded.
For additional load options, use the LoadLibraryEx function.

MSDN > GetProcAddress Function :
Retrieves the address of an exported function or variable from the specified dynamic-link library (DLL).

使用 __declspec(dllexport) 從 DLL 匯出 :
在新版本的編譯器中,您可以使用 __declspec(dllexport) 關鍵字匯出 DLL 的資料、函式、類別或類別成員函式。__declspec(dllexport) 會將匯出指示詞加入至物件檔,這樣您就不需用到 .def 檔
在建置 DLL 時,您通常會建立標頭檔 (Header File),包含要匯出之函式的原型和/或類別,並在標頭檔的宣告中加入 __declspec(dllexport)。若要使程式更容易讀取,請為 __declspec(dllexport) 定義巨集,並在每個輸出的符號使用巨集 :
  1. #define DllExport   __declspec( dllexport )  

動態連結函式庫(Dynamic Linking Libraries,DLLs)介紹 :
使用該DLL裡面的函式可分為兩大類 :
1 、 隱式連結(Implicitly Link)
優點 :
1 、 靜態載入方式所使用到的這個DLL會在應用程式執行時載入, 然後就可以呼叫出所有由DLL中匯出的函式. 就好像是包含在程式中一般。
2 、 動作較為簡單,載入的方法由編譯器負責處理,咱們不須動腦筋。
缺點 :
1 、 當這個程式靜態載入方式所使用到的這個DLL不存在時, 這個程式在開始時就出現無法找到DLL的訊息而 導致應用程式無執行。
2 、 編譯時需要加入額外的import library.
3 、 若是要載入的DLLs一多,載入應用程式的速度會便慢.
4 、 若遇到不同品牌的C++編譯器時, 靜態載入就沒有這麼簡單, 因為當函式經過Calling Conventions的處理後, 若要使用其他品牌編譯器的DLL須得大動干戈才行.

2 、 顯式連結(Explicit Linking)
優點 :
1 、DLL只要需要時才會載入到記憶體中, 可以更有效的使用記憶體.
2 、 應用程式載入的速度較使用隱式鏈結時快, 因為當程式開始載入時並不需要把DLL給載入到行程中。
3 、 編譯時不須額外的import library檔。
4 、 讓我們可以更清楚DLL的載入流程。
缺點 :
就是得要自行連結,自然要多點code囉!
This message was edited 8 times. Last update was at 27/08/2010 16:34:34

[ Python 常見問題 ] How to shift a datetime object by 12 hours in python

Source From   Here   Question   Datetime   objects hurt my head for some reason. I am writing to figure out   how to shift a date time obje...