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++)中,字符“@”是不合法的,因此这样的代码不能编译。直到编译器厂商开始行动之前,我们只有忍耐。
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 );
}
}
// Save off the window's X,Y coordinates for next time
RECT rect;
GetWindowRect( hDlg, &rect );
GetSetPositionInfoFromRegistry( TRUE, (LPPOINT)&rect );
EndDialog(hDlg, 0);
}
// 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;
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));
// 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;
}
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;
}
// 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))
// 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 ];
// 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;
}
}
// 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))
// 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 ];
// 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 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文件都包含的符号表。当符号表中出现重复信息时,我就跳过那些记录。但读取外部符号时,不得不使用其它方法。