5

Background

I have a C astronomical library that I want to use in my C++ application.

I've built it in Visual Studio 2012 Express in both Win32 and x64 configurations, and:

  • dynamic debug (.dll)
  • dynamic release (.dll)
  • static debug (.lib)
  • static release (.lib)

...so that is 2 * 4 = 8 total binaries (not counting *.pdb files, etc.)

Then I use Batch Build to build all configurations, as sometimes I need different versions and I find getting this all done at the start and with a process is better than haphazardly when it's easy to mix things up.

Well in my C++ app, I have the same process, and link to the library based on the name. Specifically, in my project properties Linker -> Input field, I have:

SwissEphemeris_$(Platform)_$(Configuration).lib

...and the Additional Library Directories is properly set. Everything seems right. The library files are in the library directory.

Here is the trouble:

Out of the 8 total configurations I have, all are linking and building properly except for two:

  1. Win32 dynamic debug
  2. Win32 dynamic release

For both of these configurations, the same linker error:

main.obj : error LNK2019: unresolved external symbol _swe_close referenced in function _main

I have tried some things to diagnose or fix, but none have worked:

  1. rebuilding the library from scratch in a new Solution/Project, ensuring no deviation between Win32 and x64 other than the /MACHINE linker flag
  2. creating a new fresh Solution/Project with nothing but a main() that called this one library function swe_close() in Win32 dynamic debug configuration and linked to that *.lib.

The error is always the same, as shown above. I've turned on verbose linker output, and what really puzzles me is that the linker seems to successfully find and read the SwissEphemeris_Win32_DynamicDebug.lib file, but still cannot find the swe_close() symbol within it. Even when dumpbin.exe shows that symbol, among all the other ones I need, are in it.

1>  Unused libraries:
1>    E:\Data\Code\lib\SwissEphemeris\SwissEphemeris_Win32_DynamicDebug.lib
1>    C:\Program Files (x86)\Windows Kits\8.0\lib\win8\um\x86\user32.lib
1>    C:\Program Files (x86)\Windows Kits\8.0\lib\win8\um\x86\gdi32.lib
1>    C:\Program Files (x86)\Windows Kits\8.0\lib\win8\um\x86\winspool.lib
1>    C:\Program Files (x86)\Windows Kits\8.0\lib\win8\um\x86\comdlg32.lib
1>    C:\Program Files (x86)\Windows Kits\8.0\lib\win8\um\x86\advapi32.lib
1>    C:\Program Files (x86)\Windows Kits\8.0\lib\win8\um\x86\shell32.lib
1>    C:\Program Files (x86)\Windows Kits\8.0\lib\win8\um\x86\ole32.lib
1>    C:\Program Files (x86)\Windows Kits\8.0\lib\win8\um\x86\oleaut32.lib
1>    C:\Program Files (x86)\Windows Kits\8.0\lib\win8\um\x86\uuid.lib
1>    C:\Program Files (x86)\Windows Kits\8.0\lib\win8\um\x86\odbc32.lib
1>    C:\Program Files (x86)\Windows Kits\8.0\lib\win8\um\x86\odbccp32.lib
1>    E:\Programs\VS2012\VC\lib\OLDNAMES.lib
1>  
1>main.obj : error LNK2019: unresolved external symbol _swe_close referenced in function _main
1>E:\Data\Code\test\Debug\test.exe : fatal error LNK1120: 1 unresolved externals
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

scratching head

Does anyone have any idea why the linker is failing to find the symbol, and ONLY for Win32 dynamic debug/release, but working fine for all 6 other configurations?

Just to be sure that the library was indeed built for Win32/X86, here is the linker command-line options from the library project's properties:

(paths are slightly different than the paths above--where the linker attempts to find the library--because the library is first built to this 'bin' directory and then copied to the library directory, which is definitely happening.)

/OUT:"E:\Data\Code\libBuilders\SwissEphemeris\bin\SwissEphemeris_Win32_DynamicDebug.dll" 
/MANIFEST 
/NXCOMPAT 
/PDB:"E:\Data\Code\libBuilders\SwissEphemeris\bin\SwissEphemeris_Win32_DynamicDebug.pdb" 
/DYNAMICBASE 
/IMPLIB:"E:\Data\Code\libBuilders\SwissEphemeris\bin\SwissEphemeris_Win32_DynamicDebug.lib" 
/DEBUG 
/DLL 
/MACHINE:X86 
/SAFESEH 
/PGD:"E:\Data\Code\libBuilders\SwissEphemeris\bin\SwissEphemeris_Win32_DynamicDebug.pgd" 
/SUBSYSTEM:CONSOLE 
/MANIFESTUAC:"level='asInvoker' uiAccess='false'" 
/ManifestFile:"E:\Data\Code\libBuilders\SwissEphemeris\obj\SwissEphemeris_Win32_DynamicDebug\SwissEphemeris_Win32_DynamicDebug.dll.intermediate.manifest" 
/ERRORREPORT:PROMPT 
/NOLOGO 

Output from dumpbin.exe /exports

E:\Data\Code\lib\SwissEphemeris>dumpbin /exports SwissEphemeris_Win32_DynamicDebug.dll
Microsoft (R) COFF/PE Dumper Version 11.00.50727.1
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file SwissEphemeris_Win32_DynamicDebug.dll

File Type: DLL

  Section contains the following exports for SwissEphemeris_Win32_DynamicDebug.dll

    00000000 characteristics
    528041A6 time date stamp Sun Nov 10 19:32:06 2013
        0.00 version
           1 ordinal base
         131 number of functions
         131 number of names

    ordinal hint RVA      name

          1    0 00001195 _swe_azalt@40 = @ILT+400(_swe_azalt@40)
          2    1 000011FE _swe_azalt_d@28 = @ILT+505(_swe_azalt_d@28)
          3    2 000012AD _swe_azalt_rev@24 = @ILT+680(_swe_azalt_rev@24)
          4    3 00001357 _swe_azalt_rev_d@20 = @ILT+850(_swe_azalt_rev_d@20)
          5    4 0000126C _swe_calc@24 = @ILT+615(_swe_calc@24)
          6    5 000011BD _swe_calc_d@20 = @ILT+440(_swe_calc_d@20)
          7    6 0000105F _swe_calc_ut@24 = @ILT+90(_swe_calc_ut@24)
          8    7 00001235 _swe_calc_ut_d@20 = @ILT+560(_swe_calc_ut_d@20)
          9    8 00001389 _swe_close@0 = @ILT+900(_swe_close@0)
         10    9 00001212 _swe_close_d@4 = @ILT+525(_swe_close_d@4)
         ...

Where the DLL is defined for export in the library header file. Only MAKE_DLL and PASCAL are defined, so the only statements here that are active are the #define PASCAL_CONV PASCAL and the #else /* 32bit DLL */ block.

/* DLL defines */
#ifdef MAKE_DLL
  #if defined (PASCAL)
    #define PASCAL_CONV PASCAL 
  #else
    #define PASCAL_CONV
  #endif
  #ifdef MAKE_DLL16 /* 16bit DLL */
    /* We compiled the 16bit DLL for Windows 3.x using Borland C/C++ Ver:3.x
       and the -WD or -WDE compiler switch. */
    #define EXP16 __export 
    #define EXP32 
  #else /* 32bit DLL */
    /* To export symbols in the new DLL model of Win32, Microsoft 
       recommends the following approach */ 
    #define EXP16 
    #define EXP32  __declspec( dllexport )
  #endif
#else 
  #define PASCAL_CONV 
  #define EXP16 
  #define EXP32 
#endif 

...Then the actual function swe_close() declaration looks like:

ext_def( void ) swe_close(void);

They use a lot of macro footwork, so this resolves to:

extern __declspec(dllexport) void far PASCAL swe_close();

I am unfamiliar with the far and the PASCAL. Could these be interfering with anything? And why would the x64 configuration work fine with this, but the Win32 break?

Ned
  • 293
  • 1
  • 2
  • 13
  • 1
    what are the options you are using with dumpbin.exe? Does the listing actually show that _swe_close is in the file when you use the /SYMBOLS option? – Richard Chambers Nov 11 '13 at 03:53
  • Good question. I was not using the /SYMBOLS option, but instead the /EXPORTS option, which does show the symbol in there. /SYMBOLS does not show the symbol swe_close() (nor many others) in there, but when I tried /SYMBOLS on the other library configurations that *do* work, it showed the same lack of many of the symbols. /EXPORTS seems to show them all. – Ned Nov 11 '13 at 04:05
  • 2
    It could well be that you have a mismatch of calling conventions. The linker is trying to import `swe_close()` with `__cdecl` calling convention (see [Name Decoration](http://msdn.microsoft.com/en-us/library/deaxefa7.aspx)). Does this match the export signature? – IInspectable Nov 11 '13 at 09:06
  • Thanks for bringing that up. All of my C library configurations and all of my C++ project configurations are set to `__cdecl` convention. I cannot find any mixing of calling convention. This is really strange. Is there another setting or flag we are not thinking of that could be the culprit? One note is that the C library is naturally set to /TC (compile as C code). Maybe that is obvious but in case it might help. – Ned Nov 12 '13 at 01:52
  • @RichardChambers @IInspectable I've pasted the results of `dumpbin` to the post. – Ned Nov 12 '13 at 03:14
  • 2
    @nedshares, I see your question has been answered. Here is a bit more info. The far indicates that the function address generated should be a far or 32 bit address of 16 bit segment and 16 bit offset and the PASCAL indicates to use the Pascal calling convention rather than the C calling convention. The use of these indicates the package is pretty old. far is no longer used and PASCAL tends to be used with some Windows API calls. See this http://en.wikipedia.org/wiki/X86_calling_conventions – Richard Chambers Nov 12 '13 at 23:20

1 Answers1

6
 9    8 00001389 _swe_close@0 = @ILT+900(_swe_close@0)

There's a clear mismatch between the function name in the DLL and the one that the linker is looking for. The DLL project has it compiled to an __stdcall function, you can tell from the @0 decoration, the linker is looking for a __cdecl function.

That can be caused by a compiler option, /Gd vs /Gz but that's unlikely and you already checked it. The more likely cause is the .h file where the exported functions are declared so that another project can #include it and use the DLL. That's usually done with macro soup so that the __declspec(dllexport) and __declspec(dllimport) attributes are correct for usage. And the calling convention should always be explicitly declared, typically with another macro. Like the way the WINAPI macro is used in Windows SDK headers. I'll put a buck on that soup being the cause.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Thanks @HansPassant. I will look further into this. I had been thinking it might be something along these lines of macros defined and not defined in the library. It's something I'm still learning to understand. I found the one `__declspec(dllexport)` location in the entire library (in the main header) and pasted its surrounding statements in the post above if it helps. I am trying to learn about these conventions myself. – Ned Nov 12 '13 at 11:05
  • I'm thinking about ripping the `FAR` and `PASCAL` out of the entire library--or at least the macros. Looking here [what is FAR PASCAL?](http://stackoverflow.com/questions/2774171/what-is-far-pascal), it says they are from old-school code that might be 16bit. Maybe I can modernize this library and make it better for my modern usage. – Ned Nov 12 '13 at 11:25
  • 4
    It is **exactly** as I guessed, PASCAL is the same as __stdcall in 32-bit code and your header incorrectly defines it only when MAKE_DLL is defined. Score one for psychic debugging :) – Hans Passant Nov 12 '13 at 11:29
  • 1
    Just confirmed! Finally, my C++ app linked correctly with the library in Win32 dynamic debug/release when I defined `PASCAL` and `FAR` to be nothing. Some of these libraries and code out there can be so tricky and unmaintainable. – Ned Nov 12 '13 at 11:36