2010年7月26日 星期一
(轉)決戰未解析的外部符號,C++實例教程,C++系列教程,C++
決戰「未解析的外部符號」
作者:Matt Pietrek
問
我用Delphi寫了一些32位代碼並把它編譯成了OBJ文件。我想 把這個Delphi代碼與由Visual C++ 編譯的C++代碼混合起來用。但是,我卻得到了類似下面的未解析符號的鏈接器錯 誤: D2.OBJ : error LNK2001: unresolved external symbol "MessageBeep"
有問題的函數看起來好像只有Windows API函數。然而,當我用Borland C++ 編譯時,一切工作正常。這到底是為什麼呢?
答
啊,鏈接器錯誤。這是我特別喜歡的方面。我花費了很多時間來解決這些非常耗時間的問題。現在,我已經有了一套行之有效的辦法來快速解決這些「未解析的外部符號」之類的問題。同時,如果你知道一些這方面的基本知識,那我解釋起來就不困難了。
可 能這只是我的感覺,但是我確實發現現在的程序員都使用非常好用的開發環境,很少有人知道它們的高級語言代碼是如何變成可執行的機器代碼的。像OBJ和 LIB之類的文件對大多數程序員來說都是黑盒。當一切工作正常時,你可能確實不需要知道從你的代碼被送到編譯器到在磁盤上產生一個可執行文件這段時間內到 底發生了什麼。但是,如果出現了一些什麼問題,這些黑盒很可能是就是你找到問題所在的惟一線索。
我要告訴你兩個關於C/C++ 編程的基本事實。我一直把問題歸結到這些事實上,並且總能找到答案(至少是在遇到「未解析的外部符號」這樣的鏈接器錯誤時是這樣)。第一個事實是,如果你跨編譯單元(一個文件就是一個編譯單元)引用符號,鏈接器看到的符號名必須完全匹配。
現 在給出一個具體的例子。假定你的一個源文件A.C中有函數Foo的實現代碼,並且,由A.C生成的OBJ文件中這個函數的名字還是Foo。用鏈接器的說法 就是,名字Foo是文件A.OBJ中的一個公共符號。現在,假定你在另外一個名字為B.CPP的文件中調用函數Foo。當你在B.CPP中調用函數Foo 時,編譯器並不知道函數Foo的實現代碼在哪裡。在這種情況下,編譯器在B.OBJ文件中生成一個記錄。這個記錄告訴鏈接器,它需要用函數Foo的真實地 址來修正對函數Foo的調用。這個記錄被稱為外部符號定義,因為函數Foo的位置是在調用它的源文件的外部。鏈接器的主要工作之一就是匹配,或者說是「解 析(resolve)」公共符號(像包含在文件A.OBJ中的公共符號)的外部定義(像文件B.OBJ中的符號Foo)。
在這個例子中,對鏈接器最重要的並不是在你的源文件中調用了什麼函數。相反,惟一重要的事就是公共符號的名字必須與外部名字完全匹配。如果它們並不完全匹配,你就會得到令人害怕的「未解析的外部符號」這樣的鏈接器錯誤。
第 二個基本事實是,編譯器背著你偷偷改變了符號的名字。例如,當生成OBJ文件時,C編譯器在符號的名稱前加一個下劃線再放入OBJ文件中。因此,在A.C 中的函數Foo在A.OBJ中的公共符號是_Foo。另外一個例子是當你使用C++時,編譯器把函數的參數信息也添加到了函數名上。在Visual C++ 中,函數「void Foo(int i)」變成了「?Foo@@YAXH@Z」。這種重命名方法被稱為名字粉碎(mangling或decorating),主要是為了讓鏈接器區分重載的函 數。(重載函數是名字相同,但參數不同的函數。記住這些,你就會理解鏈接器是怎樣處理重載的C++函數的。)
現在,我們的兩個事實說明公共符號與 外部符號的名字在鏈接階段必須匹配,還有就是,編譯器改變了符號名。當你遇到「未解析的外部符號」這樣的鏈接器消息時,要立即採取的行動再明顯不過了:找 出OBJ或LIB文件中的公共符號名,然後與鏈接器不能接受的符號名比較。它們幾乎總是不相同的,解決這個問題的方法就是讓這些符號名匹配。
回到前面的例子,假定函數Foo在B.CPP文件中的原型如下:
void Foo(int i);
如 果我鏈接A.OBJ與B.OBJ時,會有一個鏈接器錯誤,為什麼呢?因為在A.OBJ文件中,Foo的公共名字是_Foo,但是在B.OBJ文件(由 B.CPP生成)中被粉碎後的函數名字是?Foo@@YAXH@Z。這清楚地表明了那兩個事實:編譯器在兩個源文件中都改變了符號名,從而導致符號名不匹 配。
在這種情況下,你可以用extern 「C」機制來解決這個問題。也就是說,把B.CPP中的函數原型改成
extern void Foo(int i);
extern "C"告訴編譯器不要粉碎函數Foo的名字,按C編譯器的做法來(在OBJ文件中,放一個「_」在函數名前使它變成_Foo)。這樣,兩個名字匹配,從而解決了錯誤。
怎 樣才能知道OBJ文件中的外部符號名稱,從而改好自己的代碼呢?Visual C++ 附帶了一個DUMPBIN程序,它可以顯示由Visual C++創建的OBJ文件和LIB文件的內容(還有其它東西)。如果你運行DUMPBIN,記得要帶上/symbols參數才能看到所有的符號名。 Borland編譯器附帶了一個程序叫TDUMP,它可以用於Borland生成的OBJ文件和LIB文件。要想更容易地解決問題而不使用DUMPBIN 或TDUMP,繼續往下讀。我在本月專欄後面的部分提供了自己的工具。
如果要使基於Delphi的代碼與Visual C++ 共同工作,又該如何呢?很明顯,幾乎所有的Win32函數都被定義成__stdcall類型。除了還指示參數傳遞習慣外,__stdcall類型的函數的 名字已經被Visual C++ 修改得Delphi和Borland C++ 都不認識了。準確地說,Visual C++ 在__stdcall類型的函數的名字前加了一個「_」,在名字的最後加上了「@xxx」。xxx是所有實際通過堆棧傳遞給函數的參數的大小。因 此,MessageBeep(UINT uType)變成了_MessageBeep@4。同樣,GetMessageA,它帶了四個參數,變成了_GetMessageA@16。一些程序員把 這種重命名方法叫做__stdcall名字粉碎,但它與C++名字粉碎是截然不同的。
雖然Visual C++ 認為__stdcall類型的函數的名字已經被粉碎了,但Borland編譯器並不這麼認為。因此,Delphi生成的OBJ引用 MessageBeep,而MessageBeep不在Visual C++ 使用的USER32.LIB導入庫中,導入庫中的公共符號是_MessageBeep@4。Mirosoft鏈接器認為這兩個名字不匹配,因此產生了一個 鏈接器錯誤。如果你混合Borland C++ 代碼與Microsoft Visual C++ 代碼,你會遇到同樣的問題。
使事情更複雜的 是,當__stdcall類型的函數的名字出現在DLL的導出表中時,Microsoft並不粉碎它。在內部,Visual C++在你的OBJ文件中把MessageBeep函數粉碎成_MessageBeep@4,但是USER32.DLL(MessageBeep函數的代 碼就在其中)導出的名字卻是MessageBeep。這允許Borland編譯的代碼(它不粉碎__stdcall類型的函數的名字)可以正確地鏈接 Win32 DLL的導出函數。也就是說,當把名字放入DLL的導出表中時,Visual C++ 去掉了前導的「_」和後續的 「@xxx」。
怎 樣才能混合使用這兩個廠商的代碼呢?不幸的是,沒有什麼我們能做的。你的第一反應可能是在Delphi代碼中調用函數_MessageBeep@4。同樣 不幸的是,在Delphi(或C++)中,字符「@」是不合法的,因此這樣的代碼不能編譯。直到編譯器廠商開始行動之前,我們只有忍耐。
問
不知出於什麼原因,我不能在Microsoft和Borland的32位編譯器之間混合使用OBJ文件和LIB文件。然而,在16位編譯器上可以正常工作。這到底是為什麼呢?
答
讓 我們先把目光對準OBJ文件,然後再說LIB文件。從PC出現到第一個Microsoft Win32編程工具出現,幾乎所有編譯器生成的OBJ文件都是Intel OMF格式。與OMF格式的OBJ文件打交道並不是一件輕鬆的事,因此,我並沒有打算詳細描述它。最初的Windows NT開發小組使用的OBJ文件格式被稱為通用目標文件格式(Common Object File Format,COFF),而COFF格式是UNIX System V的正式機器代碼格式。使用COFF相對容易。COFF格式的OBJ與可移植可執行(Portable Executable,PE)文件的格式非常接近,而可移植可執行文件格式又是Win32的可執行文件格式。COFF格式的鏈接器從COFF格式的文件創 建EXE或DLL需要做的工作比從Intel OMF格式的文件要少。
就像有OMF和COFF格式的OBJ文件一樣,LIB文件也有OMF格式與COFF格式之分。幸運的是,這兩種格式的LIB文件都是僅僅把相應格式的一些OBJ文件放在一起組成的單個文件。專用記錄中的附加信息可以讓鏈接器快速從LIB文件中找到所需的OBJ文件。
混 合使用不同編譯器廠商的OBJ文件和LIB文件的問題是,並非每個廠商都把它的32位編譯器轉換到了COFF格式。Borland和Symantec仍舊 使用OMF格式的OBJ文件和LIB文件,但是Microsoft的32位編譯器生成COFF格式的OBJ文件和LIB文件。MASM 6.11默認情況下生成OMF格式的文件令人感到困惑,但使用/coff開關可以生成COFF格式的OBJ文件。
當鏈接不同格式的文件時,每個人 可以猜猜鏈接器會做什麼。例如,如果需要,Visual C++ 鏈接器可以把OMF格式的OBJ文件轉換成COFF格式,但它遇到OMF格式的LIB文件時就拒絕工作。Borland的TLINK始終拒絕使用COFF 格式的OBJ文件和LIB文件,Symantec C++ 7.2也是如此。Watcom 10.5好像選擇的是COFF。結果混合不同編譯器生成的文件經常造成混亂。鏈接器產生的模糊的錯誤信息並幫不了什麼忙。
即使你不混合使用不同編 譯器生成的OBJ文件,你仍然會在混合使用由不同編譯器生成的EXE和DLL時遇到問題。問題來自不同的導入庫,這些導入庫是一些非常小的OBJ文件的集 合,能夠告訴鏈接器某個特定的函數在正在鏈接的EXE或DLL之外的哪個DLL中。如果你提供了一個DLL,但不知道使用這個DLL的用戶使用的是哪個編 譯器,這樣,不同的LIB文件格式就會導致問題。大多數情況下你都得提供兩種不同格式的導入庫,一種是COFF格式,另一種是OMF格式。問題是,你怎樣 才能創建這些導入庫呢?
如果你曾為Windows 3.x編過程序,你可能使用過編譯器附帶的一個叫做IMPLIB的工具。IMPLIB接受一個DLL作為輸入,生成一個OMF格式的導入庫。IMPLIB 是通過讀取它處理的DLL的導出節來達到上述效果的。因此,如果你使用像Borland C++ 或Symantec C++ 之類的編譯器,你可以在任何你想鏈接的DLL上運行IMPLIB,這樣就能得到合適格式的LIB文件。
可惜!32位版的Visual C++ 並沒有附帶像IMPLIB之類的工具。這是為什麼呢?一個很好的解釋就是由於文章前面提到的__stdcall類型的函數的名字粉碎。DLL導出的函數名 字並不包含任何有關此函數所帶參數個數的信息,因此,假定有這樣一個IMPLIB,它也不知道怎樣生成合適的__stdcall類型的名字(例 如,_MessageBeep@4)。
幸運的是,在有些情況下,你可以使用一些鮮為人知技巧。不過這有些亂,並且僅適用於_cdecl類型的函 數,不適用於__stdcall類型的函數。如果你想鏈接到某個DLL上,就創建一個相應的DEF文件。在這個DEF文件中,有一個EXPORTS節,所 有需要包含在生成的LIB文件中的函數的名字都要在這個節中。不要在名字前加一個「_」字符,因為它會被自動加上。創建完DEF文件後,運行 Microsoft的32位LIB工具,帶上/MACHINE和/DEF選項。例如,要為MYDLL.DLL創建一個導入庫,你可以先創建 MYDLL.DEF文件,然後運行
LIB /MACHINE:i386 /DEF:MYDLL.DEF
如果一切順利,這會創建一個名字叫MYDLL.LIB的COFF格式的導入庫,。
OBJHELP程序
由 於本月的專欄中回答的兩個問題都牽涉到OBJ、LIB以及它們中的符號,所以我寫了一個叫OBJHELP的工具。不管是OBJ文件還是LIB文件,也不管 是COFF格式還是Intel-OMF格式,OBJHELP都能顯示出文件的類型。更重要的是,OBJHELP能顯示出文件中的公共符號和外部符號的準確 名字。這對你跟蹤解決「未解析的外部符號」之類的鏈接器錯誤是非常有幫助的。例如,你可以運行OBJHELP的兩個實例。一個檢查有未解析的外部符號的 OBJ文件。另一個顯示你認為所需代碼應該在其中的庫或OBJ文件。如果名字不匹配,你的鏈接器可能不合適。
圖1是一幅OBJHELP實際工作的 畫面。頂端左邊的編輯框中顯示當前正在被顯示的文件名稱。你可以以三種方式選擇文件:鍵入文件名,然後回車;使用「Browse」按鈕來瀏覽;或者你也可 以把OBJ或LIB文件拖到OBJHELP窗口。(哎,我不得不為此學習拖放操作!)
圖1 OBJHELP程序
文件信息被顯示在兩個列表框中。上面的列表框顯示文件中所有的公共符號,下面的列表框顯示所有的外部符號。如果一些符號名看起來特別彆扭,那很可能是C++的名字粉碎所致。我有意不把那些名字還原為它們的本來面目,因為我想讓你看一看鏈接器在解析符號時都看到了什麼。
關 於你看到的外部符號有一些重要的注意事項。首選,如果你顯示一個LIB文件,那麼相同的符號可能在「Extrens」列表框中顯示多次。當LIB文件中包 含多個OBJ文件,並且它們中好幾個引用相同的外部函數時會出現這種情況。第二,當抓取COFF格式的導入庫時,函數名可能有一個「__imp__」前綴 (例如,__imp__GetFocus@0)。這是在函數定義時使用__declspec(dllimport)編譯器指令產生的。我在1995年十二 月的專欄中講過__declspec(dllimport)的工作原理,因此,我不打算再重複。
當顯示OMF格式的導入庫時,OBJHELP把符 號所在的DLL放在符號名前面(例如,USER32.dll.GETFOCUS)。我之所以這麼做是因為Borland編譯器把許多系統DLL的導入信息 組合成單個的庫(IMPORT32.LIB)。相反,Microsoft和Symantec為每一個DLL生成一個導入庫 (KERNEL32.LIB,USER32.LIB等等)。
OBJHELP代碼(如圖2所示)可以分成兩部分。用戶界面代碼在 OBJHELP.CPP中。這是相當簡單的基於對話框的用戶界面。其餘的文件用於鑑別文件類型以及找出文件中的公共符號與外部符號。可能大多數程序員對 OBJ文件和LIB文件的格式不是很感興趣。所以我就不詳細描述了。誰要是感興趣可以讀一讀源代碼(加了詳細的註釋)。
2010年7月20日 星期二
(轉)最快的嘸蝦米輸入法安裝 @ Fedora 10 - NICA
一、下載 liu.bin ,將檔案置於本機 /usr/share/scim/tables/ 下
二、在桌面環境的 scim 圖示 (Fedora 10 桌面右上角鍵盤圖示) 點選滑鼠右鍵,選擇 『重新載入設定值』即可。
小無:試過了,Fedora 13也OK哦!
2010年7月16日 星期五
(cite)Enable or disable always save webpages in the default encoding option in Word 2010
How to enable or disable always save webpages in the default encoding option in Word 2010
Microsoft Word 2010 allows its user to create webpages and do customization according to their choice for the created webpages. Various options regarding this are provided by Word 2010. Also there is an option for always saving webpages in the default encoding. This option when enabled, always saves the webpages you are creating in the default encoding assigned according to Word 2010 standard for a webpage. You can enable or disable this option as per your need/requirement. Trace under mentioned steps to accomplish the task of enabling or disabling always save webpages in the default encoding option, yourself.
Steps to enable or disable always save webpages in the default encoding option in Word 2010: -
1. It is mandatory to open the "Word Options" dialog box, so as to proceed further. The dialog box for "Word Options" can be opened by following either of the under mentioned ways, but first click on the "Microsoft Office button" then trace any one step among step a) or step b).
a) Click on the button by name "Options". You will find it located under the tab by name "Word".
Image 1
OR
b) Click on the tab by name "Word" and look towards its adjacent side for "Options" tab and then click on that tab. You will find the tab located under "Microsoft Tools" section.
Image 2
2. Now, as the "Word Options" dialog box is open you are ready to proceed further. First click on "Advanced" tab present in left part of dialog box of "Word Options" and then search for "General" section. Click on "Web Options..." button located in the "General" section, to open dialog box of "Web Options".
Image 3
3. From the "Web Options" dialog box, click on the "Encoding" tab, and then click in the checkbox for "Always save Web pages in the default encoding" option, to put a right mark (i.e. check) in it. You will find the options located below the "Default editor" section of the "Web Options" dialog box. Checking the checkbox enables the option "Always save Web pages in the default encoding". Click "OK" button of the dialog box of "Web Options" afterwards.
OR
From the "Web Options" dialog box, click on the "Encoding" tab, and then click in the checkbox for "Always save Web pages in the default encoding" option, to clear it (i.e. uncheck). You will find the options located below the "Default editor" section of the "Web Options" dialog box. Unchecking the checkbox disables the option "Always save Web pages in the default encoding". Click "OK" button of the dialog box of "Web Options" afterwards.
Image 4
4. Click on "OK button of the dialog box of "Word Options" to close it.
Image 5
After tracing the above mentioned steps, you are now capable to enable or disable "Always save Web pages in the default encoding" option in Word 2010.
無:原來word 2010 的web page encode設定就是這麼簡單!
2010年7月15日 星期四
(轉)植物能思考記憶 機制類似神經系統-Yahoo!奇摩新聞
2010/07/15 12:22NewTalk 新頭殼
新頭殼newtalk 2010.07.15 陳怡潔/編譯報導
植物有思考能力,或許已非新 聞。但現在科學家發現,植物可以對光線中的信息做出反應,並透過葉片傳遞信息,更能記憶之前的事件。植物這種機制和神經系統非常相似。
科學 家說,葉片可以就光線強度和性質進行判讀,並經由葉片傳遞信息。這種電化訊號就像植物的神經系統一樣。實驗中,科學家發現整株植物體都會對照到葉片上的光 線起反應。而且這種光誘反應,會持續在黑暗中作用,好像植物體能「記憶」一般。
華沙大學的Stanislaw Karpinski教授在布拉格年度生物學協會會議上發表此項研究。之前的研究發現植物體會對周遭環境反應。但這次他們又發現,接受到的化學訊號會引起 「階梯」式的反應進而影響整株植物。植物的「神經系統」可能已經被發現。
有趣的一點是,照到不同顏色的光,是影響植物反應的關鍵。 Karpinski教授說,這可能是可以保護植物,讓植株免於免疫疾病的機制。他說,用某種色光照植物一個小時後,可以讓它們在接下來的24小時中免於被 疾病感染。
這表示植物能記憶色光,並依此建立免疫系統,預備面對不同狀況。植物靠著光線中的訊息,對不同免疫部分進行加強,對抗不同季節的 狀況和特有的傳染病。
科學界人士對此研究成果讚譽有加,咸認其在此領域跨出了一大步。
OS:這樣說來,吃素也是殺害有思考的東西了…
PNotes Portable | PortableApps.com - Portable software for USB drives
超好用免費便利貼軟体。
因為Note Mania在本人Vista x86會當機,所以個人覺得比Note Mania好用。
2010年7月14日 星期三
Note Mania新手需知
但是預設會在60天後,自動刪除便條,
如果你喜歡在同一個便利貼修修改改,記得
到Options -> Preferences -> Misc. -> Delete data more than ...
把這個選項取消,
才不會到時候便條突然不見了哦!
Oracle VM Virtual Box 使用全螢幕的方法
2.此時,會載入iso在CD-ROM下,安裝所需的檔案即可。
PS. Linux下,需先安裝dkms, gcc
2010年7月13日 星期二
(cite)noarch - What is noarch
2010年7月12日 星期一
(轉)int 与 byte[] 的相互转换 - 沐枫小筑 - 博客园
int 與 byte[] 的相互轉換 - 沐楓小築 - 博客園:
1. 最普通的方法
- 從byte[] 到 uint
b = new byte[] {0xfe,0x5a,0x11,0xfa};
u = (uint)(b[0] | b[1] << 8 | b[2] << 16 | b[3] << 24); - 從int 到 byte[]
b[0] = (byte)(u);
b[1] = (byte)(u >> 8);
b[2] = (byte)(u >> 16);
b[3] = (byte)(u >> 24);
2. 使用 BitConverter (強力推薦)
- 從int 到byte[]
byte[] b = BitConverter.GetBytes(
0xba5eba11 );
//{0x11,0xba,0x5e,0xba} - 從byte[]到int
uint u = BitConverter.ToUInt32(
new byte[] {0xfe, 0x5a, 0x11,
0xfa},0 ); // 0xfa115afe
3. Unsafe代碼 (雖然簡單,但需要更改編譯選項)
unsafe
{
// 從int 到byte[]
fixed ( byte* pb = b );
// 從byte[] 到 int
u = *((uint*)pb);
}
4. 使用Marshal類
byte[] b= new byte[4]{1,2,3,4};
//從byte[] 到 int
Marshal.Copy(b, 0, ptr, 4);
int u = Marshal.ReadInt32(ptr);
//從int 到byte[]
Marshal.WriteInt32(ptr, u);
Marshal.Copy(ptr,b,0,4);
Marshal.FreeHGlobal(ptr); // 最後要記得釋放內 存
使用第4種看起來比較麻煩,實際上,如果想把結構(struct)類型轉換成byte[],則第4種是相當方便的。例如:
MyStruct o;
byte[] arr = new byte[len];//{};
IntPtr ptr = Marshal.AllocHGlobal(len);
try
{
// 從byte[] 到struct MyStruct
Marshal.Copy(arr, index, ptr, Math.Min(length, arr.Length - index));
o = (MyStruct)Marshal.PtrToStructure(ptr, typeof(MyStruct));
// 從struct MyStruct 到 byte[]
Marshal.StructureToPtr(o, ptr, true); // 使用時要注意fDeleteOld參數
Marshal.Copy(ptr, arr, 0, len);
}
finally
{
Marshal.FreeHGlobal(ptr);
}
return o;