Keywords: Visual Studio 2015 | Unresolved External Symbol | SDL2 Linking Error
Abstract: This article provides a comprehensive examination of the unresolved external symbol errors for __imp__fprintf and __imp____iob_func encountered when compiling SDL2 projects in Visual Studio 2015. By analyzing the evolution of Microsoft's C Runtime Library (CRT) from earlier versions to VS2015, it reveals how changes in the definitions of stdin, stdout, and stderr macros lead to linking issues. The article systematically explains the role of the __iob_func function, the transformation of the FILE structure, and its impact on binary compatibility. Two primary solutions are presented: adding the legacy_stdio_definitions.lib library or implementing a custom __iob_func. Additionally, it discusses third-party library compatibility concerns and risk mitigation strategies, offering developers a thorough technical reference.
Problem Background and Error Manifestation
When compiling SDL2-based projects with Visual Studio 2015, developers often encounter the following linking errors:
1>SDL2main.lib(SDL_windows_main.obj) : error LNK2019: unresolved external symbol __imp__fprintf referenced in function _ShowError
1>SDL2main.lib(SDL_windows_main.obj) : error LNK2019: unresolved external symbol __imp____iob_func referenced in function _ShowError
1>E:\Documents\Visual Studio 2015\Projects\SDL2_Test\Debug\SDL2_Test.exe : fatal error LNK1120: 2 unresolved externals
These errors indicate that the linker cannot find definitions for the __imp__fprintf and __imp____iob_func symbols. Although developers may have correctly configured the linking paths for SDL2 libraries, the root cause lies in significant changes to the Microsoft C Runtime Library (CRT) in Visual Studio 2015.
Technical Analysis of CRT Changes
Prior to Visual Studio 2015, the standard I/O stream macros were defined as follows:
#define stdin (&__iob_func()[0])
#define stdout (&__iob_func()[1])
#define stderr (&__iob_func()[2])
Here, the __iob_func() function returns a pointer to an array of FILE structures containing three elements corresponding to stdin, stdout, and stderr. The function prototype was typically declared as:
_CRTIMP FILE * __cdecl __iob_func(void);
However, in Visual Studio 2015, Microsoft redesigned the CRT implementation:
#define stdin (__acrt_iob_func(0))
#define stdout (__acrt_iob_func(1))
#define stderr (__acrt_iob_func(2))
The new __acrt_iob_func() function directly returns FILE* handles instead of array pointers. More critically, the definition of the FILE structure underwent fundamental changes:
// Visual Studio 2008 and earlier
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
// Visual Studio 2015
typedef struct _iobuf
{
void* _Placeholder;
} FILE;
This alteration in structure layout breaks binary compatibility. Libraries compiled with older CRT versions (e.g., SDL2main.lib) expect __iob_func() to return an array of complete FILE structures, which the VS2015 CRT no longer provides.
Solution 1: Using legacy_stdio_definitions.lib
The simplest solution recommended by Microsoft is to add legacy_stdio_definitions.lib to the linker input. This library contains compatibility implementations of functions from older CRT versions. Configuration steps include:
- Right-click the project in Visual Studio and select "Properties"
- Navigate to "Linker" → "Input" → "Additional Dependencies"
- Add
legacy_stdio_definitions.libto the list
This approach works for most scenarios, especially when projects depend on third-party libraries compiled with older Visual Studio versions. According to Microsoft documentation, the printf and scanf family functions were made inline in VS2015, contributing to the linking errors.
Solution 2: Custom __iob_func Implementation
If legacy_stdio_definitions.lib is unavailable or finer control is needed, a custom __iob_func() function can be implemented. A common example is:
FILE _iob[] = {*stdin, *stdout, *stderr};
extern "C" FILE * __cdecl __iob_func(void)
{
return _iob;
}
However, this method has potential issues. Since the FILE structure in VS2015 is simplified to only a void* placeholder, while old code expects the full structure layout, memory access errors may occur. Specifically:
- Each element in the
_iobarray compiled with VS2015 is 4 bytes (32-bit) or 8 bytes (64-bit) in size - Old code expects the
FILEstructure to be 32 bytes or larger - When old code attempts to access
_iob[1], it calculates an offset of 32 bytes, but_iob[1]is actually offset by only 4-8 bytes in memory
Thus, while this custom implementation might work in some cases, it is not a fully reliable solution.
In-depth Technical Details and Compatibility Considerations
Technically, the __imp__ prefix indicates that these symbols are referenced as Windows DLL functions via import libraries. When the linker encounters __imp__fprintf, it expects to find a pointer to the actual implementation of the fprintf function.
Regarding third-party library compatibility, Microsoft advises:
- Recompile all static libraries with the latest Visual Studio version whenever possible
- If third-party library source code is unavailable, request updated binaries from the vendor
- Alternatively, encapsulate the old library in a separate DLL compiled with the older compiler
For SDL2 specifically, the issue often arises in SDL_windows_main.obj, which contains Windows-specific entry point code that uses fprintf for error output.
Practical Recommendations and Conclusion
Based on the analysis, we recommend the following practical strategies:
- Preferred Solution: Add
legacy_stdio_definitions.libto linker dependencies, as it is Microsoft's officially supported compatibility solution. - Update Library Files: If possible, obtain SDL2 library files compiled with VS2015 or later.
- Compilation Configuration Check: Ensure consistent settings in project properties under "C/C++" → "Code Generation" → "Runtime Library" (e.g.,
/MDor/MT). - Version Management: In team development environments, unifying tool versions can prevent such compatibility issues.
Understanding the underlying causes of these linking errors helps developers better handle similar problems. The CRT changes reflect Microsoft's efforts to optimize C++ runtime performance and security, albeit with temporary compatibility challenges. As development tools evolve, keeping codebases updated is key to ensuring long-term compatibility.