Click Here to Install Silverlight*
United StatesChange|All Microsoft Sites
MSDN
|Developer Centers|Library|Downloads|Code Center|Subscriptions|MSDN Worldwide
Search for


Advanced Search
MSDN Home > MSJ

July 1996

Microsoft Systems Journal Homepage

Matt Pietrek is the author of Windows 95 System Programming Secrets (IDG Books, 1995). He works at NuMega Technologies Inc., and can be reached at 71774.362@compuserve.com.

QI've written some 32-bit Delphi code and compiled it to an OBJ file. I'm trying to mix this Delphi code with C++ code compiled by Visual C++". The problem is that I get unresolved symbol linker errors like this:

 D2.OBJ : error LNK2001: unresolved external symbol "MessageBeep"

The only functions that seem to have trouble are Windows" API functions. However, when I compile my code with Borland C++, all works well. What's the story here?

AAh, linker errors. A personal favorite of mine. I certainly spend my share of the work day resolving these time-sucking horrors. Luckily, I have an established routine for locating the source of "unresolved external" errors quickly, and it isn't hard to explain once you understand the basics.

Maybe it's just me, but it seems fashionable these days for programmers to wrap themselves in cozy development environments and not know how their high-level language code becomes executable machine code. Things like OBJ and LIB files are black boxes to most programmers. When things work well, you really don't need to know what happens between sending your compiler code and finding an executable file on disk. But when something breaks, those black boxes are often your only clue regarding what's wrong.

With that said, I'll get off my soapbox and tell you two fundamental truths about C/C++ programming. I've always returned to these truths and found an answer (at least when it came to "unresolved external" linker errors). The first truth is that, if you reference a symbol across compilation units, the symbol names the linker sees must match exactly. (OK, you can fudge this by telling the linker to be case-insensitive, but you get the point.)

To give a concrete example, let's say you have source for a function called Foo in the file A.C. Furthermore, in the OBJ file generated from A.C, the function appears with the name Foo. To use linker parlance, the name Foo is a public symbol residing in A.OBJ. Now, let's say you want to call function Foo from another creatively named source file like B.CPP. When you call Foo from B.CPP, the compiler doesn't know where the real code for Foo resides. In this situation the compiler emits a record in B.OBJ. This record tells the linker it needs to fix up the call to function Foo with Foo's real address. This record is called an external symbol definition since the location of Foo is external to the source module that called it. One of the linker's primary jobs is to match up or "resolve" external definitions (like B.OBJ has for the symbol Foo) with public symbols (like that contained in A.OBJ).

In this example, what's ultimately important to the linker is not what you call the function in your source files. Rather, the only thing that matters is that the names of the public and external names match exactly. If they don't match exactly, you get the dreaded "unresolved external" linker error.

The second fundamental truth is that compilers change symbol names behind your back. For example, C compilers prepend an underbar to the symbol name as it appears in the OBJ file. Thus, function Foo in A.C appears as the public symbol _Foo in A.OBJ. Another example is when you use C++; the compiler takes the function name and adds additional information about the function's parameters. In Visual C++, the function "void Foo(int i)" becomes "?Foo@@YAXH@Z". This renaming is called mangling or decorating, and allows the linker to differentiate between overloaded functions. (Overloaded functions are functions that have the same name, but different parameter lists. With this in mind, you can see how the linker deals with overloaded C++ functions.)

Now, our two truths state that public and external symbol names must match in the link phase, and compilers change symbol names. When you come across an "unresolved external" linker message, the immediate course of action should be obvious: find the public symbol name in the OBJ or LIB file, then compare that to the external symbol name the linker complained about. They're almost always different, and solving the error is a matter of getting the symbol names to match.

Returning to my previous example, let's say that in I have the following prototype for Foo in B.CPP:

 void Foo(int i);

If I try to link A.OBJ and B.OBJ I get a linker error. Why? Because Foo's public name is _Foo in A.OBJ, but the mangled function name is ?Foo@@YAXH@Z in B.OBJ (which came from B.CPP). This shows both truths clearly: the compiler changed the symbol name in both source modules, and the resulting symbol names didn't match.

In this case, you can resolve the error by using the extern "C" mechanism. That is, in B.CPP change the prototype to

 extern "C" void Foo(int i);

The extern "C" tells the compiler not to mangle Foo's name, but to treat it as a C compiler would (prepending "_" to make the function name _Foo in the OBJ file). This resolves the error by making both names match.

How can you figure out the public and external symbol names in OBJ files, thereby bringing bliss to your coding endeavors? Visual C++ comes with the DUMPBIN program, which displays the contents of OBJ and LIB files (among other things) created by Visual C++. If you run DUMPBIN, make sure to use the /symbols argument to see all the symbol names. Borland compilers come with a program called TDUMP, which works with Borland-produced OBJ and LIB files. For something a little easier to use than DUMPBIN or TDUMP, keep reading. I've provided my own utility later in this month's column.

How does all this apply to mixing Delphi-based code with Visual C++? As it turns out, nearly all Win32" functions are defined as __stdcall functions. Aside from dictating a parameter-passing convention, the names of __stdcall functions are modified by Visual C++ in a fashion that neither Delphi nor Borland C++ recognize. Specifically, Visual C++ adds "_" onto the beginning and "@xxx" onto the end of the __stdcall function. The xxx is actually the size of the stack arguments passed to the function. Thus, MessageBeep(UINT uType) becomes _MessageBeep@4. Likewise, GetMessageA, which takes 4 parameters, becomes _GetMessageA@16. Some programmers refer to this renaming as __stdcall mangling, but it's definitely different from C++ name mangling.

Visual C++ expects the names of __stdcall functions to be mangled, while Borland compilers don't. Thus, the Delphi-produced OBJ has an external reference to just MessageBeep. MessageBeep isn't in the USER32.LIB import library that Visual C++ uses, but the public symbol _MessageBeep@4 is. The Microsoft" linker can't match up these two names, so there's your linker error. For what it's worth, the same problem appears if you try to mix Borland C++ code with Microsoft Visual C++ code.

To make matters just a bit more confusing, Microsoft's mangling of __stdcall function names is undone when the symbol name appears in the export table of a DLL. That is, while the MessageBeep function is mangled to _MessageBeep@4 internally by Visual C++ in your OBJ files, the exported name in USER32.DLL (where MessageBeep's code resides) is just MessageBeep. This allows Borland-compiled code (which doesn't mangle __stdcall function names) to link properly with functions exported by the Win32 DLLs. Visual C++, on the other hand, effectively strips the preceding "_" and following "@xxx" when it puts the symbol name into a DLL's export table.

What can you do about mixing code from the two vendors? Unfortunately, not much. Your first reaction might be to cheat in the Delphi code and call a function called _MessageBeep@4. Unfortunately, the "@" character isn't valid in a Delphi (or C++) identifier, so the code won't compile. Until compiler vendors start singing from the same songbook, we're stuck with issues like this.

QFor some reason, I can't seem to mix and match OBJ or LIB files between the Microsoft and Borland 32-bit compilers. However, this worked fine with 16-bit compilers. What happened?

ALet me first address OBJs specifically, and then move on to LIB files later. From the dawn of the PC until the first Microsoft Win32 programming tools appeared, almost all compilers produced OBJ files in the Intel OMF format. OMF-format OBJ files are not the easiest things in the world to work with, so I won't even attempt a technical description here. The original Windows NT" team worked with an object module format known as Common Object File Format (COFF), which is the official machine-code format for UNIX System V. COFF is relatively easy to work with. COFF format OBJs also have the advantage of being much closer in format to Portable Executable files, the native executable format for Win32. A COFF-format linker should have much less work to create an EXE or DLL from a COFF file than from an Intel OMF-format file.

Just as there are OMF and COFF-format OBJs, there are also OMF and COFF-format LIB files. Mercifully, both LIB formats are just collections of the corresponding format OBJ files bundled together in a single file. Additional information in special records of the LIB file lets the linker quickly determine which OBJ files it needs to use from the LIB file.

The problem with mixing OBJs and LIBs from different compiler vendors is that not every vendor switched to COFF format for its 32-bit compilers. Borland and Symantec still use OMF-style OBJs and LIBs, while Microsoft's
32-bit compilers produce COFF-format OBJs and LIBs. Adding to the confusion, MASM 6.11 produces OMF files
by default, but the /coff switch tells MASM to emit COFF OBJs instead.

When it comes time to link files with different formats, it's anybody's guess what a linker will do. For example, the Visual C++ linker can convert an OMF-style OBJ into COFF if necessary, but it refuses to work with OMF-style LIB files. Borland's TLINK steadfastly refuses to work with COFF-format OBJ or LIB files, as is the case with Symantec C++ 7.2. Watcom 10.5 appears to prefer COFF. The result is that mixing files from different compilers often creates a mess. Cryptic error messages from the linkers don't help.

Even if you're not mixing OBJs from different compilers, you can still run into problems when mixing EXEs and DLLs produced by different compilers. The problem comes from differing import libraries, which are collections of very small OBJs that tell a linker that the code for a particular function resides in some DLL external to the EXE or DLL being linked. Different LIB file formats cause problems if you provide a DLL and you don't know which compiler the consumer of the DLL will be using. In most cases you have to supply two different import libraries, one in COFF format and the other in OMF format. The question is, how do you create these import libraries?

If you did any programming for Windows 3.x, you probably used a tool called IMPLIB that came with your compiler. IMPLIB accepts a DLL as input and emits an OMF-style import library. IMPLIB does it's magic by reading the exports section of the DLL it processes. Thus, if you use a compiler like Borland C++ or Symantec C++, you can run IMPLIB on any 32-bit DLL that you want to link and get back an appropriate format LIB file.

Alas, 32-bit versions of Visual C++ don't come with anything like IMPLIB. Why not? One very good reason is the __stdcall mangling that I mentioned at the beginning of this column. The exported names in a DLL don't contain any information on the number of arguments the function takes, so a hypothetical COFF-format IMPLIB won't know how to generate the proper __stdcall names (for example, _MessageBeep@4).

Luckily, there is a little-known trick you can use in some situations. It's a little messy and it only works with _cdecl functions, not with __stdcall functions. Create a DEF file for the DLL that you want to link with. In the DEF file, include an EXPORTS section that contains the names of the functions to be included in the library. Don't precede the names with "_" characters since they will be added automatically. After you create the DEF file, run the Microsoft 32-bit LIB utility with the /MACHINE and /DEF switches. For example, to create an import library for MYDLL.DLL, you'd make MYDLL.DEF and invoke

 LIB /MACHINE:i386 /DEF:MYDLL.DEF

If all goes well, this creates a COFF import library called MYDLL.LIB.

OBJHELP

Since both questions answered in this month's column have involved OBJs, LIBs, and symbols within them, I wrote a nifty utility called OBJHELP. For any OBJ or LIB file, be it COFF or Intel-OMF based, OBJHELP displays the type of file. More importantly, OBJHELP shows you the exact names of all public and external symbols in the file. This can be very helpful when you're trying to track down those pesky "unresolved external" linker errors. For instance, you could run two instances of OBJHELP. In one, examine the OBJ with the unresolved external. In the other, display the library or OBJ that you believe the code should be in. If the names don't match, you'll probably have an unhappy linker.

Figure 1 shows OBJHELP in action. The edit control at the top left shows the name of the file currently being displayed. You can select a file in three ways: you can type the name of the file and hit Enter; you can browse for a file with the Browse button; or you can be cool and drag an OBJ or LIB file from someplace like Explorer and drop it on the OBJHELP window. (Hey, I had to learn drag and drop sometime!)

Figure 1 OBJHELP

Whenever OBJHELP displays a file, the two list boxes are filled with information. The top list box shows all the public symbols in the file and the bottom list box shows all the external symbols. If some of the symbol names look like garbage, it's most likely because of C++ name mangling. I purposefully didn't unmangle them because I wanted you to see the same strings that the linker sees when trying to resolve things.

A couple of points are important to note about the external symbols you see. First, the same symbol may show up multiple times in the Externs list box if you're displaying a LIB file. This happens when the LIB file contains multiple OBJ files that have external references to the same function. Second, when dumping COFF import libraries, function names may be preceded by "__imp__" (for example, __imp__GetFocus@0). This is a byproduct of using __declspec(dllimport) with a function definition. I described how __declspec(dllimport) works in my November 1995 column, so I won't go over the gory details here.

When showing an OMF-style import library, OBJHELP precedes the symbol name with the name of the DLL from which the symbol comes (for instance, USER32.dll.GETFOCUS). I did this because Borland combines the import information from numerous system DLLs into a single library (IMPORT32.LIB). In contrast, Microsoft and Symantec have separate import libraries for each DLL (KERNEL32.LIB, USER32.LIB, and so on).

The OBJHELP code itself (see Figure 2) can be split into two parts. The user interface code resides in OBJHELP.CPP. This is a fairly straightforward dialog-based user interface. The remaining files are for identifying the type of file specified and finding the public and external symbols within the file. I suspect the formats of OBJ and LIB files is of limited interest to most programmers, so I'll skip a blow-by-blow description. Those of you who are interested can read the code and figure out what I did (it's reasonably well commented).

It is worth noting how I find the public and external symbols, however. In OMF files, I simply scan all the records and display only those names in PUBDEF, PUBD32, and EXTDEF records. For COFF OBJs and LIBs I use the symbol table that every OBJ contains. COFF LIBs have two records that list all the public symbols for quick lookup by the linker. I skip those records though, as the same information appears in the OBJ symbol tables. I have to read the symbol tables anyway to find the external symbols.

I didn't forget all you command-line tool types-I'm one myself. Specifying the HARDCORE=1 option when building OBJHELP.MS causes a program called DUMPOBJ to be built instead of OBJHELP. That is, "NMAKE HARDCORE=1 OBJHELP.MS" creates DUMPOBJ.EXE, which is a console-mode application. DUMPOBJ.EXE takes a single command-line argument (the name of the OBJ or LIB you want to display) and writes its output to stdout. This is nice for redirecting the output to a file.

 DUMPOBJ.EXE d:\mstools\lib\comctl32.lib > myfile

In myfile, the output begins like this:

 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: _AddMRUData@12
public: __imp__AddMRUData@12
extern: __IMPORT_DESCRIPTOR_COMCTL32
<rest of file omitted>

In an ideal world, compilers and linkers would have a "Do what I mean" setting. Until then, we'll have to wrestle with issues like getting symbol names straight and figuring out what types of OBJ or LIB files you're dealing with.

Have a question about programming in Windows? You can mail it directly to Under the Hood, Microsoft Systems Journal, 825 Eighth Avenue, 18th Floor, New York, New York 10019, or send it to MSJ(re: Under the Hood) via:


Internet:

Matt Pietrek
71774.362@compuserve.com

From the July 1996 issue of Microsoft Systems Journal.

© 2015 Microsoft Corporation. All rights reserved. Contact Us |Terms of Use |Trademarks |Privacy & Cookies
Microsoft