譯 者: zhzhtst
時 間: 2007-08-18,02:08
鍊 接: http://bbs.pediy.com/showthread.php?t=49875
決戰未解析的外部符号
相信大家在程式設計時都遇到過這樣的錯誤提示資訊:未能解析的外部符号。Matt Pietrek在MSJ雜志1996年7月的Under the Hood專欄中對許多讀者請教的這個問題做了回答。
注:附件中是本文的PDF格式,可讀性好
決戰“未解析的外部符号”
作者: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)”變成了“[email protected]@[email protected]”。這種重命名方法被稱為名字粉碎(mangling或decorating),主要是為了讓連結器區分重載的函數。(重載函數是名字相同,但參數不同的函數。記住這些,你就會了解連結器是怎樣處理重載的C++函數的。)
現在,我們的兩個事實說明公共符号與外部符号的名字在連結階段必須比對,還有就是,編譯器改變了符号名。當你遇到“未解析的外部符号”這樣的連結器消息時,要立即采取的行動再明顯不過了:找出OBJ或LIB檔案中的公共符号名,然後與連結器不能接受的符号名比較。它們幾乎總是不相同的,解決這個問題的方法就是讓這些符号名比對。
回到前面的例子,假定函數Foo在B.CPP檔案中的原型如下:
void Foo(int i);
如果我連結A.OBJ與B.OBJ時,會有一個連結器錯誤,為什麼呢?因為在A.OBJ檔案中,Foo的公共名字是_Foo,但是在B.OBJ檔案(由B.CPP生成)中被粉碎後的函數名字是[email protected]@[email protected]。這清楚地表明了那兩個事實:編譯器在兩個源檔案中都改變了符号名,進而導緻符号名不比對。
在這種情況下,你可以用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)變成了[email protected]。同樣,GetMessageA,它帶了四個參數,變成了[email protected]。一些程式員把這種重命名方法叫做__stdcall名字粉碎,但它與C++名字粉碎是截然不同的。
雖然Visual C++ 認為__stdcall類型的函數的名字已經被粉碎了,但Borland編譯器并不這麼認為。是以,Delphi生成的OBJ引用MessageBeep,而MessageBeep不在Visual C++ 使用的USER32.LIB導入庫中,導入庫中的公共符号是[email protected]。Mirosoft連結器認為這兩個名字不比對,是以産生了一個連結器錯誤。如果你混合Borland C++ 代碼與Microsoft Visual C++ 代碼,你會遇到同樣的問題。
使事情更複雜的是,當__stdcall類型的函數的名字出現在DLL的導出表中時,Microsoft并不粉碎它。在内部,Visual C++在你的OBJ檔案中把MessageBeep函數粉碎成[email protected],但是USER32.DLL(MessageBeep函數的代碼就在其中)導出的名字卻是MessageBeep。這允許Borland編譯的代碼(它不粉碎__stdcall類型的函數的名字)可以正确地連結Win32 DLL的導出函數。也就是說,當把名字放入DLL的導出表中時,Visual C++ 去掉了前導的“_”和後續的 “@xxx”。
怎樣才能混合使用這兩個廠商的代碼呢?不幸的是,沒有什麼我們能做的。你的第一反應可能是在Delphi代碼中調用函數[email protected]。同樣不幸的是,在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類型的名字(例如,[email protected])。
幸運的是,在有些情況下,你可以使用一些鮮為人知技巧。不過這有些亂,并且僅适用于_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__”字首(例如,[email protected])。這是在函數定義時使用__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檔案的格式不是很感興趣。是以我就不較長的描述了。誰要是感興趣可以讀一讀源代碼(加了詳細的注釋)。
圖2 OBJHELP
OBJHELP.CPP
//==================================
// OBJHELP - Matt Pietrek 1996
// FILE: OBJHELP.CPP
//==================================
#include <windows.h>
#include <shellapi.h>
#include <commdlg.h>
#include <stdarg.h>
#pragma hdrstop
#include "OBJHELP.H"
#include "dumpobj.h"
// Helper functions
void Handle_WM_COMMAND(HWND hDlg, WPARAM wParam, LPARAM lParam);
void Handle_WM_INITDIALOG(HWND hDlg);
void Handle_WM_CLOSE( HWND hDlg );
void Handle_WM_DropFILES( HWND hDlg, WPARAM wParam );
BOOL Handle_Browse( HWND hWndDlg );
BOOL CALLBACK ObjHelpDlgProc( HWND, UINT, WPARAM, LPARAM );
void GetSetPositionInfoFromRegistry( BOOL fSave, POINT *lppt );
void ProcessNewFile( void );
BOOL fProcessingLib = FALSE;
HWND g_hDlg = 0;
HWND g_hPublicsListBox = 0;
HWND g_hExternsListBox = 0;
unsigned g_cAveLBCharWidth = 0;
unsigned cbMaxPublics=0; // longest string in publics listbox
unsigned cbMaxExterns=0; // longest string in externs listbox
// ======================= String literals ===============================
char gszRegistryKey[] = "Software//WheatyProductions//ObjHelp";
char g_AboutMsgTxt[] =
"OBJHELP displays the public and external symbols in OBJ and LIB files."
"It works with both COFF and Intel OMF format files./r/n/r/n"
"Files can be displayed via the Browse button, or by dragging a file onto "
"the program's window./r/n/r/n"
"For more information about OBJHELP, refer to the July 1996 Microsoft "
"Systems Journal, or the Microsoft Developer Network CD.";
char g_AboutTitleTxt[] = "OBJHELP - Matt Pietrek 1996, for MSJ";
// ======================= Start of code ===============================
int PASCAL WinMain( HANDLE hInstance, HANDLE hPrevInstance,
PSTR lpszCmdLine, int nCmdShow )
{
// Bring up the user interface (A dialog box? What a surprise!)
DialogBox(hInstance, "ObjHelpDlg", 0, (DLGPROC)ObjHelpDlgProc);
return 0;
}
BOOL CALLBACK ObjHelpDlgProc(HWND hDlg,UINT msg,WPARAM wParam,LPARAM lParam)
{
switch ( msg )
{
case WM_COMMAND:
Handle_WM_COMMAND( hDlg, wParam, lParam ); return TRUE;
case WM_INITDIALOG:
Handle_WM_INITDIALOG( hDlg ); return TRUE;
case WM_DropFILES:
Handle_WM_DropFILES( hDlg, wParam ); return 0;
case WM_CLOSE:
Handle_WM_CLOSE( hDlg ); break;
// let everything else fall through
}
return FALSE;
}
void Handle_WM_COMMAND( HWND hDlg, WPARAM wParam, LPARAM lParam )
{
switch ( LOWORD(wParam) )
{
case IDC_BUTTON_BROWSE:
if ( Handle_Browse( hDlg ) )
ProcessNewFile();
break;
case IDC_BUTTON_HELP:
MessageBox( hDlg, g_AboutMsgTxt, g_AboutTitleTxt, MB_OK );
break;
case IDOK:
if ( GetFocus() == GetDlgItem(hDlg, IDC_EDIT_FILENAME) )
ProcessNewFile();
break;
}
return;
}
void Handle_WM_INITDIALOG(HWND hDlg)
{
// Get the window coordinates where ObjHelp.EXE was last running,
// and move the window to that spot.
POINT pt;
GetSetPositionInfoFromRegistry( FALSE, &pt );
SetWindowPos(hDlg, 0, pt.x, pt.y, 0, 0,
SWP_NOSIZE | SWP_NOREDRAW | SWP_NOZORDER | SWP_NOACTIVATE);
// Set us up to accept dropped filenames
DragAcceptFiles( hDlg, TRUE );
g_hDlg = hDlg; // Save off the hDlg in a global variable
g_hPublicsListBox = GetDlgItem( hDlg, IDC_LIST_PUBLIC_SYMBOLS );
g_hExternsListBox = GetDlgItem( hDlg, IDC_LIST_EXTERN_SYMBOLS );
// Figure out how wide characters in the listboxes will be
HDC hDCLB = GetDC( g_hPublicsListBox );
if ( hDCLB )
{
TEXTMETRIC tm;
if ( GetTextMetrics(hDCLB, &tm) )
g_cAveLBCharWidth = tm.tmAveCharWidth;
ReleaseDC( g_hPublicsListBox, hDCLB );
}
}
void Handle_WM_CLOSE( HWND hDlg )
{
// Stop accepting dropped filenames
DragAcceptFiles( hDlg, TRUE );
// Save off the window's X,Y coordinates for next time
RECT rect;
GetWindowRect( hDlg, &rect );
GetSetPositionInfoFromRegistry( TRUE, (LPPOINT)&rect );
EndDialog(hDlg, 0);
}
void Handle_WM_DropFILES( HWND hDlg, WPARAM wParam )
{
char szFileName[MAX_PATH];
UINT cbFileName;
// Get the name of the file that was dropped on us, the release the HDrop
cbFileName = DragQueryFile((HDrop)wParam,0,szFileName,sizeof(szFileName));
DragFinish( (HDrop)wParam );
if ( fProcessingLib ) // If we're already processing, don't bother
return;
SetDlgItemText( hDlg, IDC_EDIT_FILENAME, szFileName );
ProcessNewFile();
}
BOOL Handle_Browse( HWND hDlg )
{
OPENFILENAME ofn;
char szFilename[512] = "";
static char szFilter1[] = "OBJS (*.OBJ)/0*.OBJ/0LIBS (*.LIB)/0*.LIB/0";
memset(&ofn, 0, sizeof(OPENFILENAME));
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = hDlg;
ofn.lpstrFilter = szFilter1;
ofn.nFilterIndex = 1;
ofn.lpstrFile = szFilename;
ofn.nMaxFile = sizeof(szFilename);
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
if ( GetOpenFileName(&ofn) )
{
SetDlgItemText( hDlg, IDC_EDIT_FILENAME, szFilename );
return TRUE;
}
return FALSE;
}
void GetSetPositionInfoFromRegistry( BOOL fSave, POINT *lppt )
{
HKEY hKey;
DWORD dataSize, err, disposition;
char szKeyName[] = "DlgCoordinates";
if ( !fSave ) // In case the key's not there yet, we'll
lppt->x = lppt->y = 0; // return 0,0 for the coordinates
// Open the registry key (or create it if the first time being used)
err = RegCreateKeyEx( HKEY_CURRENT_USER, gszRegistryKey, 0, 0,
REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS,
0, &hKey, &disposition );
if ( ERROR_SUCCESS != err )
return;
if ( fSave ) // Save out coordinates
{
RegSetValueEx( hKey, szKeyName, 0, REG_BINARY,
(PBYTE)lppt, sizeof(*lppt) );
}
else // read in coordinates
{
dataSize = sizeof(*lppt);
RegQueryValueEx( hKey, szKeyName, 0, 0, (PBYTE)lppt, &dataSize );
}
}
#define PREFIX_SKIP 8 // Skip first 8 characters of output
int output( char *format, ... )
{
va_list argptr;
va_start( argptr, format );
char szBuffer[1024];
int result = wvsprintf(szBuffer, format, argptr); // Format the string
unsigned cbOutput = 0;
if ( result > PREFIX_SKIP)
cbOutput = result - PREFIX_SKIP;
// Decide which listbox this output is going to
HWND hWndDest = 0;
if ( 0 == strnicmp(szBuffer, "public: ", PREFIX_SKIP) )
{
hWndDest = g_hPublicsListBox;
cbMaxPublics = (cbOutput > cbMaxPublics) ? cbOutput : cbMaxPublics;
}
else if ( 0 == strnicmp(szBuffer, "extern: ", PREFIX_SKIP) )
{
hWndDest = g_hExternsListBox;
cbMaxExterns = (cbOutput > cbMaxExterns) ? cbOutput : cbMaxExterns;
}
// Add the string to the appropriate listbox
if ( hWndDest )
SendMessage(hWndDest, LB_ADDSTRING, 0, (LPARAM)(szBuffer+PREFIX_SKIP));
va_end( argptr );
return result;
}
void ProcessNewFile( void )
{
char szFileName[MAX_PATH];
fProcessingLib = TRUE;
GetDlgItemText( g_hDlg, IDC_EDIT_FILENAME, szFileName,
sizeof(szFileName) );
HCURSOR hOldCursor = SetCursor( LoadCursor(0,IDC_WAIT) );
// Clear out the listboxes
SendMessage( g_hPublicsListBox, LB_RESETCONTENT, 0, 0 );
SendMessage( g_hExternsListBox, LB_RESETCONTENT, 0, 0 );
// Turn off listbox updating
SendMessage( g_hPublicsListBox, WM_SETREDRAW, 0, 0 );
SendMessage( g_hExternsListBox, WM_SETREDRAW, 0, 0 );
cbMaxPublics = cbMaxExterns = 0;
// Fill with new information
OBJ_FILE_TYPE fileType = DisplayObjectFile( szFileName );
// Set the horizontal width of the listboxes so they scroll if necessary
SendMessage( g_hPublicsListBox, LB_SETHORIZONTALEXTENT,
(g_cAveLBCharWidth * (cbMaxPublics+4)), 0 );
SendMessage( g_hExternsListBox, LB_SETHORIZONTALEXTENT,
(g_cAveLBCharWidth * (cbMaxExterns+4)), 0 );
// Turn listbox updating back on
SendMessage( g_hPublicsListBox, WM_SETREDRAW, TRUE, 0 );
SendMessage( g_hExternsListBox, WM_SETREDRAW, TRUE, 0 );
PSTR pszFileType;
switch ( fileType )
{
case OBJ_COFF_OBJ: pszFileType = "COFF OBJ"; break;
case OBJ_COFF_LIB: pszFileType = "COFF LIB"; break;
case OBJ_OMF_OBJ: pszFileType = "OMF OBJ"; break;
case OBJ_OMF_LIB: pszFileType = "OMF LIB"; break;
case OBJ_OMF_IMPLIB: pszFileType = "OMF IMPORT LIB"; break;
default: pszFileType = "UNKNOWN FILE TYPE"; break;
}
SetDlgItemText( g_hDlg, IDC_STATIC_DESCRIPTION, pszFileType );
SetCursor( hOldCursor );
fProcessingLib = FALSE;
}
DUMPOBJ.CPP
//==================================
// OBJHELP - Matt Pietrek 1996
// FILE: DUMPOBJ.CPP
//==================================
#include <windows.h>
#pragma hdrstop
#include "dumpobj.h"
#include "memmapfl.h"
OBJ_FILE_TYPE DisplayObjectFile( PTSTR pszFileName )
{
MEMORY_MAPPED_FILE mmf( pszFileName );
PBYTE pFileBase = (PBYTE)mmf.GetPointerToMemory();
if ( !pFileBase )
{
output( "Couldn't open mapping for %s/n", pszFileName );
return OBJ_UNKNOWN;
}
if ( 0 == strncmp((PSTR)pFileBase, IMAGE_ARCHIVE_START,
IMAGE_ARCHIVE_START_SIZE) )
{
DumpCOFFLibFile( pFileBase );
return OBJ_COFF_LIB;
}
else if ( IMAGE_FILE_MACHINE_I386 == *(PWORD)pFileBase )
{
DumpCOFFObjFile( pFileBase );
return OBJ_COFF_OBJ;
}
else if ( (0xF0 == *(PBYTE)pFileBase) || (0x80 == *(PBYTE)pFileBase) )
return DumpIntelOMFFile( pFileBase );
else
return OBJ_UNKNOWN;
}
#ifdef CMDLINE
int output( char *format, ... )
{
va_list argptr;
va_start( argptr, format );
char szBuffer[1024];
int result = wvsprintf(szBuffer, format, argptr);
strcat( szBuffer, "/n" ); // Tack on a newline for the cmdline output
WriteFile( GetStdHandle(STD_OUTPUT_HANDLE), szBuffer,
result+1, (PDWORD)&result, 0 ); //+1 for the newline we added
va_end( argptr );
return result;
}
int main( int argc, char * argv[] )
{
output( "DUMPOBJ - Matt Pietrek 1996, for Microsoft Systems Journal" );
if ( argc <= 1 )
{
output( "syntax: DUMPOBJ <filename>/n" );
return 1;
}
DisplayObjectFile( argv[1] );
return 0;
}
#endif
DUMPCOFF.CPP
//==================================
// OBJHELP - Matt Pietrek 1996
// FILE: DUMPCOFF.CPP
//==================================
#include <windows.h>
#include <stdlib.h>
#include <string.h>
#pragma hdrstop
#include "dumpobj.h"
// MakePtr is a macro that allows you to easily add to values (including
// pointers) together without dealing with C's pointer arithmetic. It
// essentially treats the last two parameters as DWORDs. The first
// parameter is used to typecast the result to the appropriate pointer type.
#define MakePtr( cast, ptr, addValue ) (cast)( (DWORD)(ptr) + (DWORD)(addValue))
void DumpCOFFObjFile( PBYTE pFileBase )
{
PIMAGE_FILE_HEADER pFileHdr = (PIMAGE_FILE_HEADER)pFileBase;
// Bail out if it's not an Intel OBJ. Sorry...!
if ( IMAGE_FILE_MACHINE_I386 != pFileHdr->Machine )
return;
PIMAGE_SYMBOL pSymbol = MakePtr( PIMAGE_SYMBOL,
pFileHdr, pFileHdr->PointerToSymbolTable );
// Pointer math at work here!
PIMAGE_SYMBOL pSymbolEnd = pSymbol + pFileHdr->NumberOfSymbols;
// Point at the string table, which immediately follows the symbol table
PSTR pStringTable = (PSTR)pSymbolEnd;
while ( pSymbol < pSymbolEnd )
{
// We only care about storage class "EXTERNAL"
if ( IMAGE_SYM_CLASS_EXTERNAL == pSymbol->StorageClass )
{
// First, let's get a pointer to the name
PSTR pszName;
char szShortNameBuff[ sizeof(pSymbol->N) + 1 ];
if ( pSymbol->N.Name.Short )
{
pszName = szShortNameBuff;
memcpy( pszName, &pSymbol->N, sizeof(pSymbol->N) );
pszName[8] = 0;
}
else
pszName = pStringTable + pSymbol->N.Name.Long;
// strings starting with "??_" are string literals. Ignore them
if ( 0 == strncmp( pszName, "??_", 3 ) )
goto next;
if ( IMAGE_SYM_UNDEFINED == pSymbol->SectionNumber )
output( "extern: %s", pszName );
else
output( "public: %s", pszName );
}
next:
pSymbol += pSymbol->NumberOfAuxSymbols + 1; // Pointer math here!
}
}
void DumpCOFFLibFile( PBYTE pFileBase )
{
PIMAGE_ARCHIVE_MEMBER_HEADER pArchHdr;
// Advance past the "!<arch>/n" that starts a COFF library
pArchHdr = (PIMAGE_ARCHIVE_MEMBER_HEADER)
(pFileBase + IMAGE_ARCHIVE_START_SIZE);
while( 1 )
{
if ( 0 == strncmp( (PSTR)pArchHdr->Name,
IMAGE_ARCHIVE_LINKER_MEMBER, 16) )
{
; // Do nothing - it's a linker member (i.e., the dictionary)
}
else if ( 0 == strncmp( (PSTR)pArchHdr->Name,
IMAGE_ARCHIVE_LONGNAMES_MEMBER,16) )
{
; // Do nothing - it's the string pool
}
else
{
// output( "OBJ in LIB at %08X/n",
// (PBYTE)pArchHdr - (PBYTE)pFileBase );
DumpCOFFObjFile( (PBYTE)(pArchHdr + 1)); // It's an OBJ file
}
// Calculate how big this member is (it's originally stored as
// as ASCII string.
unsigned thisMemberSize = atoi((PSTR)pArchHdr->Size)
+ IMAGE_SIZEOF_ARCHIVE_MEMBER_HDR;
thisMemberSize = (thisMemberSize+1) & ~1; // Round up
// Get a pointer to the next archive member
pArchHdr = MakePtr(PIMAGE_ARCHIVE_MEMBER_HEADER, pArchHdr,
thisMemberSize);
// There's no good way to know if we hit the end of the file
// (short of knowing how big the file is, and tracking how far
// we're into it.) Thus, we'll keep going until we see garbage.
if ( IsBadReadPtr( pArchHdr, IMAGE_SIZEOF_ARCHIVE_MEMBER_HDR) )
break;
if ( strncmp((PSTR)pArchHdr->EndHeader, IMAGE_ARCHIVE_END, 2) )
break;
}
}
DUMPCOFF.CPP
//==================================
// OBJHELP - Matt Pietrek 1996
// FILE: DUMPCOFF.CPP
//==================================
#include <windows.h>
#include <stdlib.h>
#include <string.h>
#pragma hdrstop
#include "dumpobj.h"
// MakePtr is a macro that allows you to easily add to values (including
// pointers) together without dealing with C's pointer arithmetic. It
// essentially treats the last two parameters as DWORDs. The first
// parameter is used to typecast the result to the appropriate pointer type.
#define MakePtr( cast, ptr, addValue ) (cast)( (DWORD)(ptr) + (DWORD)(addValue))
void DumpCOFFObjFile( PBYTE pFileBase )
{
PIMAGE_FILE_HEADER pFileHdr = (PIMAGE_FILE_HEADER)pFileBase;
// Bail out if it's not an Intel OBJ. Sorry...!
if ( IMAGE_FILE_MACHINE_I386 != pFileHdr->Machine )
return;
PIMAGE_SYMBOL pSymbol = MakePtr( PIMAGE_SYMBOL,
pFileHdr, pFileHdr->PointerToSymbolTable );
// Pointer math at work here!
PIMAGE_SYMBOL pSymbolEnd = pSymbol + pFileHdr->NumberOfSymbols;
// Point at the string table, which immediately follows the symbol table
PSTR pStringTable = (PSTR)pSymbolEnd;
while ( pSymbol < pSymbolEnd )
{
// We only care about storage class "EXTERNAL"
if ( IMAGE_SYM_CLASS_EXTERNAL == pSymbol->StorageClass )
{
// First, let's get a pointer to the name
PSTR pszName;
char szShortNameBuff[ sizeof(pSymbol->N) + 1 ];
if ( pSymbol->N.Name.Short )
{
pszName = szShortNameBuff;
memcpy( pszName, &pSymbol->N, sizeof(pSymbol->N) );
pszName[8] = 0;
}
else
pszName = pStringTable + pSymbol->N.Name.Long;
// strings starting with "??_" are string literals. Ignore them
if ( 0 == strncmp( pszName, "??_", 3 ) )
goto next;
if ( IMAGE_SYM_UNDEFINED == pSymbol->SectionNumber )
output( "extern: %s", pszName );
else
output( "public: %s", pszName );
}
comentFlags = 0; // Don't know what this is yet.
if ( m_commentClass != 0xA0 ) // Bail out if not an OMF extension
return;
if ( 0x1 != *p ) // Bail out if not an IMPDEF
return;
comentFlags |= OMF_COMENT_IMPDEF; // It's an IMPDEF
p += 2; // Blast past the subtype and ordinal fields
// Copy the "internal" name to a buffer, then advance past it
memcpy( szName, p+1, *p );
szName[*p] = 0;
p += (*p + 1); // Advance past internal name
// Copy the module name out of the record, then advance past it.
memcpy( szModuleName, p+1, *p );
szModuleName[*p] = 0;
output( "public: %s.%s", szModuleName, szName );
}
OBJ_FILE_TYPE DumpIntelOMFFile( PBYTE pFileBase )
{
POMF_RECORD pBaseRec = (POMF_RECORD)pFileBase;
DWORD comentFlags;
// Check to see if it's a library, and if so, grab the page align size
BOOL fLib = (BOOL)(OMF_LIBHDR == pBaseRec->m_type);
unsigned cbAlign = fLib ? (pBaseRec->m_length + 3): 0; // Don't ask...
BOOL fContinue = TRUE;
while ( fContinue )
{
// output( "Record %02X at: %08X/n", pBaseRec->m_type,
// (PBYTE)pBaseRec - pFileBase );
BOOL fPageAlign = FALSE; // Round up record to next paragraph
switch( pBaseRec->m_type )
{
case OMF_EXTDEF:
((POMF_EXTDEF_RECORD)pBaseRec)->Display();
break;
case OMF_PUBDEF:
case OMF_PUBD32:
((POMF_PUBDEF_RECORD)pBaseRec)->Display();
break;
case OMF_COMENT:
((POMF_COMENT_RECORD)pBaseRec)->Display( comentFlags );
break;
case OMF_MODEND:
case OMF_MODE32:
if ( fLib )
fPageAlign = TRUE;
else
fContinue = FALSE; // Not a .LIB. We're done dumping
break;
case OMF_LIBEND:
fContinue = FALSE;
break;
default:
if ( (pBaseRec->m_type < OMF_THEADR) || // Check for bogus
(pBaseRec->m_type > OMF_LIBEND) ) // OMF records
fContinue = FALSE;
break;
}
// Point to the next record
pBaseRec = (POMF_RECORD)((DWORD)pBaseRec + pBaseRec->m_length + 3);
// If necessary, adjust up to the next paragraph (.LIB files need
// this when you encounter a MODEND record).
if ( fPageAlign )
{
// Write-only code to align to the next page-sized boundary
DWORD temp = (DWORD)pBaseRec + (cbAlign-1);
pBaseRec = (POMF_RECORD)(temp & ~(cbAlign-1));
}
}
if ( comentFlags & OMF_COMENT_IMPDEF )
return OBJ_OMF_IMPLIB;
else if ( fLib )
return OBJ_OMF_LIB;
else
return OBJ_OMF_OBJ;
}
我查找公共符号與外部符号的方法并沒有什麼。對于OMF格式的檔案,我隻是簡單地掃描所有的記錄,但是隻顯示在PUBDEF,PUBD32和EXTDEF記錄中的名字。對于COFF格式的OBJ和LIB檔案,我使用了每個OBJ檔案都包含的符号表。當符号表中出現重複資訊時,我就跳過那些記錄。但讀取外部符号時,不得不使用其它方法。
當建立OBJHELP.MS時指定HARDCORE=1選項可以産生DUMPOBJ而不生成OBJHELP。也就是說,“NMAKE HARDCORE=1 OBJHELP.MS”将建立DUMPOBJ.EXE,這是一個指令行程式。它僅有一個指令行參數(就是你想要顯示的OBJ或LIB檔案的名字),并把結果輸出到标準輸出裝置。這有利于把輸出重定向到一個檔案。
DUMPOBJ.EXE d:/mstools/lib/comctl32.lib > myfile
它的輸出像下面這個樣子:
DUMPOBJ - Matt Pietrek 1996, for Microsoft Systems Journal
public: __IMPORT_DESCRIPTOR_COMCTL32
extern: __NULL_IMPORT_DESCRIPTOR
extern: COMCTL32_NULL_THUNK_DATA
public: __NULL_IMPORT_DESCRIPTOR
public: COMCTL32_NULL_THUNK_DATA
public: [email protected]
public: [email protected]
extern: __IMPORT_DESCRIPTOR_COMCTL32
<輸出的其餘部分省略>
譯者:SmartTech