unresolved external symbol __imp__fprintf and __imp____iob_func, SDL2

141,265

Solution 1

I have finally figured out why this is happening !

In visual studio 2015, stdin, stderr, stdout are defined as follow :

#define stdin  (__acrt_iob_func(0))
#define stdout (__acrt_iob_func(1))
#define stderr (__acrt_iob_func(2))

But previously, they were defined as:

#define stdin  (&__iob_func()[0])
#define stdout (&__iob_func()[1])
#define stderr (&__iob_func()[2])

So now __iob_func is not defined anymore which leads to a link error when using a .lib file compiled with previous versions of visual studio.

To solve the issue, you can try defining __iob_func() yourself which should return an array containing {*stdin,*stdout,*stderr}.

Regarding the other link errors about stdio functions (in my case it was sprintf()), you can add legacy_stdio_definitions.lib to your linker options.

Solution 2

To Milan Babuškov, IMO, this is exactly what the replacement function should look like :-)

FILE _iob[] = {*stdin, *stdout, *stderr};

extern "C" FILE * __cdecl __iob_func(void)
{
    return _iob;
}

Solution 3

Microsoft has a special note on this (https://msdn.microsoft.com/en-us/library/bb531344.aspx#BK_CRT):

The printf and scanf family of functions are now defined inline.

The definitions of all of the printf and scanf functions have been moved inline into stdio.h, conio.h, and other CRT headers. This is a breaking change that leads to a linker error (LNK2019, unresolved external symbol) for any programs that declared these functions locally without including the appropriate CRT headers. If possible, you should update the code to include the CRT headers (that is, add #include ) and the inline functions, but if you do not want to modify your code to include these header files, an alternative solution is to add an additional library to your linker input, legacy_stdio_definitions.lib.

To add this library to your linker input in the IDE, open the context menu for the project node, choose Properties, then in the Project Properties dialog box, choose Linker, and edit the Linker Input to add legacy_stdio_definitions.lib to the semi-colon-separated list.

If your project links with static libraries that were compiled with a release of Visual C++ earlier than 2015, the linker might report an unresolved external symbol. These errors might reference internal stdio definitions for _iob, _iob_func, or related imports for certain stdio functions in the form of __imp_*. Microsoft recommends that you recompile all static libraries with the latest version of the Visual C++ compiler and libraries when you upgrade a project. If the library is a third-party library for which source is not available, you should either request an updated binary from the third party or encapsulate your usage of that library into a separate DLL that you compile with the older version of the Visual C++ compiler and libraries.

Solution 4

As answered above, the right answer is to compile everything with VS2015, but for interest the following is my analysis of the problem.

This symbol does not appear to be defined in any static library provided by Microsoft as part of VS2015, which is rather peculiar since all others are. To discover why, we need to look at the declaration of that function and, more importantly, how it's used.

Here's a snippet from the Visual Studio 2008 headers:

_CRTIMP FILE * __cdecl __iob_func(void);
#define stdin (&__iob_func()[0])
#define stdout (&__iob_func()[1])
#define stderr (&__iob_func()[2])

So we can see that the job of the function is to return the start of an array of FILE objects (not handles, the "FILE *" is the handle, FILE is the underlying opaque data structure storing the important state goodies). The users of this function are the three macros stdin, stdout and stderr which are used for various fscanf, fprintf style calls.

Now let's take a look at how Visual Studio 2015 defines the same things:

_ACRTIMP_ALT FILE* __cdecl __acrt_iob_func(unsigned);
#define stdin (__acrt_iob_func(0))
#define stdout (__acrt_iob_func(1))
#define stderr (__acrt_iob_func(2))

So the approach has changed for the replacement function to now return the file handle rather than the address of the array of file objects, and the macros have changed to simply call the function passing in an identifying number.

So why can't they/we provide a compatible API? There are two key rules which Microsoft can't contravene in terms of their original implementation via __iob_func:

  1. There must be an array of three FILE structures which can be indexed in the same manner as before.
  2. The structural layout of FILE cannot change.

Any change in either of the above would mean existing compiled code linked against that would go badly wrong if that API is called.

Let's take a look at how FILE was/is defined.

First the VS2008 FILE definition:

struct _iobuf {
        char *_ptr;
        int   _cnt;
        char *_base;
        int   _flag;
        int   _file;
        int   _charbuf;
        int   _bufsiz;
        char *_tmpfname;
        };
typedef struct _iobuf FILE;

And now the VS2015 FILE definition:

typedef struct _iobuf
{
    void* _Placeholder;
} FILE;

So there is the crux of it: the structure has changed shape. Existing compiled code referring to __iob_func relies upon the fact that the data returned is both an array that can be indexed and that in that array the elements are the same distance apart.

The possible solutions mentioned in the answers above along these lines would not work (if called) for a few reasons:

FILE _iob[] = {*stdin, *stdout, *stderr};

extern "C" FILE * __cdecl __iob_func(void)
{
    return _iob;
}

The FILE array _iob would be compiled with VS2015 and so it would be laid out as a block of structures containing a void*. Assuming 32-bit alignment, these elements would be 4 bytes apart. So _iob[0] is at offset 0, _iob[1] is at offset 4 and _iob[2] is at offset 8. The calling code will instead expect FILE to be much longer, aligned at 32 bytes on my system, and so it will take the address of the returned array and add 0 bytes to get to element zero (that one is okay), but for _iob[1] it will deduce that it needs to add 32 bytes and for _iob[2] it will deduce that it needs to add 64-bytes (because that's how it looked in the VS2008 headers). And indeed the disassembled code for VS2008 demonstrates this.

A secondary issue with the above solution is that it copies the content of the FILE structure (*stdin), not the FILE * handle. So any VS2008 code would be looking at a different underlying structure to VS2015. This might work if the structure only contained pointers, but that's a big risk. In any case the first issue renders this irrelevant.

The only hack I've been able to dream up is one in which __iob_func walks the call stack to work out which actual file handle they are looking for (based on the offset added to the returned address) and returns a computed value such that it gives the right answer. This is every bit as insane as it sounds, but the prototype for x86 only (not x64) is listed below for your amusement. It worked okay in my experiments, but your mileage may vary - not recommended for production use!

#include <windows.h>
#include <stdio.h>
#include <dbghelp.h>

/* #define LOG */

#if defined(_M_IX86)

#define GET_CURRENT_CONTEXT(c, contextFlags) \
  do { \
    c.ContextFlags = contextFlags; \
    __asm    call x \
    __asm x: pop eax \
    __asm    mov c.Eip, eax \
    __asm    mov c.Ebp, ebp \
    __asm    mov c.Esp, esp \
  } while(0);

#else

/* This should work for 64-bit apps, but doesn't */
#define GET_CURRENT_CONTEXT(c, contextFlags) \
  do { \
    c.ContextFlags = contextFlags; \
    RtlCaptureContext(&c); \
} while(0);

#endif

FILE * __cdecl __iob_func(void)
{
    CONTEXT c = { 0 };
    STACKFRAME64 s = { 0 };
    DWORD imageType;
    HANDLE hThread = GetCurrentThread();
    HANDLE hProcess = GetCurrentProcess();

    GET_CURRENT_CONTEXT(c, CONTEXT_FULL);

#ifdef _M_IX86
    imageType = IMAGE_FILE_MACHINE_I386;
    s.AddrPC.Offset = c.Eip;
    s.AddrPC.Mode = AddrModeFlat;
    s.AddrFrame.Offset = c.Ebp;
    s.AddrFrame.Mode = AddrModeFlat;
    s.AddrStack.Offset = c.Esp;
    s.AddrStack.Mode = AddrModeFlat;
#elif _M_X64
    imageType = IMAGE_FILE_MACHINE_AMD64;
    s.AddrPC.Offset = c.Rip;
    s.AddrPC.Mode = AddrModeFlat;
    s.AddrFrame.Offset = c.Rsp;
    s.AddrFrame.Mode = AddrModeFlat;
    s.AddrStack.Offset = c.Rsp;
    s.AddrStack.Mode = AddrModeFlat;
#elif _M_IA64
    imageType = IMAGE_FILE_MACHINE_IA64;
    s.AddrPC.Offset = c.StIIP;
    s.AddrPC.Mode = AddrModeFlat;
    s.AddrFrame.Offset = c.IntSp;
    s.AddrFrame.Mode = AddrModeFlat;
    s.AddrBStore.Offset = c.RsBSP;
    s.AddrBStore.Mode = AddrModeFlat;
    s.AddrStack.Offset = c.IntSp;
    s.AddrStack.Mode = AddrModeFlat;
#else
#error "Platform not supported!"
#endif

    if (!StackWalk64(imageType, hProcess, hThread, &s, &c, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL))
    {
#ifdef LOG
        printf("Error: 0x%08X (Address: %p)\n", GetLastError(), (LPVOID)s.AddrPC.Offset);
#endif
        return NULL;
    }

    if (s.AddrReturn.Offset == 0)
    {
        return NULL;
    }

    {
        unsigned char const * assembly = (unsigned char const *)(s.AddrReturn.Offset);
#ifdef LOG
        printf("Code bytes proceeding call to __iob_func: %p: %02X,%02X,%02X\n", assembly, *assembly, *(assembly + 1), *(assembly + 2));
#endif
        if (*assembly == 0x83 && *(assembly + 1) == 0xC0 && (*(assembly + 2) == 0x20 || *(assembly + 2) == 0x40))
        {
            if (*(assembly + 2) == 32)
            {
                return (FILE*)((unsigned char *)stdout - 32);
            }
            if (*(assembly + 2) == 64)
            {
                return (FILE*)((unsigned char *)stderr - 64);
            }

        }
        else
        {
            return stdin;
        }
    }
    return NULL;
}

Solution 5

I don't know why but:

#ifdef main
#undef main
#endif

After the includes but before your main should fix it from my experience.

Share:
141,265
RockFrenzy
Author by

RockFrenzy

Updated on May 22, 2021

Comments

  • RockFrenzy
    RockFrenzy about 3 years

    Could someone explain what the

    __imp__fprintf

    and

    __imp____iob_func

    unresolved external means?

    Because I get these errors when I'm trying to compile:

    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
    

    I can already say that the problem is not from linking wrong. I have linked everything up correctly, but for some reason it won't compile.

    I'm trying to use SDL2.

    I'm using Visual Studio 2015 as compiler.

    I have linked to SDL2.lib and SDL2main.lib in Linker -> Input -> Additional Dependencies and I have made sure that the VC++ Directories are correct.

  • Ross Ridge
    Ross Ridge about 9 years
    Your change is a mistake in and of itself and wouldn't have fixed the problem described in your question. It's only a coincidence that you're no longer getting linker errors, which is probably solely the result of how you rebuilt the library.
  • RockFrenzy
    RockFrenzy about 9 years
    @RossRidge, oh yeah that might just have been that. Ah well.
  • Steven R. Loomis
    Steven R. Loomis almost 9 years
    Thanks for tracking this down. IIRC the problem with {*stdin,*stdout,*stderr} might be that different compilation units might have their 'own' copy of stdin, which is why these functions were called directly.
  • Vargas
    Vargas almost 9 years
    that solved for me as well, just a reminder to use extern "C" in the declaration/definition.
  • Milan Babuškov
    Milan Babuškov almost 9 years
    Can someone write exactly what the replacement function should look like? I tried different variants and I keep getting compile errors. Thanks.
  • paulm
    paulm almost 9 years
    Just missing an #ifdef for MSVC and for MSVC version < 2015
  • PoL0
    PoL0 over 8 years
    extern "C" { FILE __iob_func[3] = { *stdin,*stdout,*stderr }; }
  • invalidusername
    invalidusername about 8 years
    How do you handle the __imp__fmode problem? I did the following but i'm not sure if that is correct. At least it compiles. extern "C" { unsigned int* __imp__fmode[1] = { 0 }; }
  • Trevor Hart
    Trevor Hart over 7 years
    ....Okay....so before trying this I audibly said to myself that I somehow doubt this is going to work yet it totally did.....can you explain why this works...?
  • The XGood
    The XGood over 7 years
    @TrevorHart I believe that it undefines a "faulty" SDL main included references to the "undefined" buffers and if yours uses your buffers, then it works all well and good.
  • Hans Olsson
    Hans Olsson over 7 years
    As MarkH notes in another answer that looks correct, but will not work.
  • bytecode77
    bytecode77 over 7 years
    Or #pragma comment(lib, "legacy_stdio_definitions.lib") - but this doesn't fix the __imp___iob_func - is there a legacy lib for this as well?
  • Akumaburn
    Akumaburn about 7 years
    Correct answer is this one, easiest fix is as stated to upgrade the project to VS2015 and then compile.
  • Mohamed BOUZIDI
    Mohamed BOUZIDI almost 7 years
    In my case, I need to upgrade many projects (C++ and C# projects) from visual studio 2013 to use Visual Studio 2015 Update 3. I want to keep VC100(Visual studio 2010 C++ compiler) when building C++ projects but I have the same errors as above. I fixed imp_fprintf by adding legacy_stdio_definitions.lib to the linker. How can i fix also _imp____iob_func ?
  • Mohamed BOUZIDI
    Mohamed BOUZIDI almost 7 years
    Before answering my previous question, Is it normal that these errors occurs when using msbuild 14 and IntelCompiler 2016 and VC100 to compile C++ projects ?
  • Cheezmeister
    Cheezmeister almost 7 years
    This is horrible bad practice hacks but it's 3 lines and it works and it saved me from having to rabbithole into building SDL so...nicely done.
  • Jesse Chisholm
    Jesse Chisholm almost 7 years
    @paulm I think you mean #if defined(_MSC_VER) && (_MSC_VER >= 1900).
  • paulm
    paulm almost 7 years
    @JesseChisholm perhaps, depends if this also applies to every known future version of MSVC or not ;)
  • The XGood
    The XGood almost 7 years
    @Cheezmeister Bad practice and hacks are often required for everything. Especially the things that shouldn't need them.
  • Hans Olsson
    Hans Olsson over 6 years
    The iob_func definition above does not work, see MarkH's answer for a correct definition. (You cannot just define a function as an array and expect calls to work.)
  • athos
    athos about 6 years
    what can i do for a x64 compilation?
  • Sisir
    Sisir about 5 years
    a simple hack: #define __iob_func __acrt_iob_func
  • Sisir
    Sisir about 5 years
    Here is a discussion on this solution: social.msdn.microsoft.com/Forums/vstudio/en-US/…
  • Antonin GAVREL
    Antonin GAVREL about 4 years
    you should add formating with ``` and also some explanations
  • Brian
    Brian about 4 years
    While this code may solve the question, including an explanation of how and why this solves the problem would really help to improve the quality of your post, and probably result in more up-votes. Remember that you are answering the question for readers in the future, not just the person asking now. Please edit your answer to add explanations and give an indication of what limitations and assumptions apply.
  • HolyBlackCat
    HolyBlackCat almost 4 years
    This can fix a different SDL-related error, but not the one OP was dealing with.
  • HolyBlackCat
    HolyBlackCat almost 4 years
    Were you getting the exact same error as OP, or unresolved external symbols?
  • Aeryes
    Aeryes almost 4 years
    Unresolved external symbols in general. Nothing worked until I added shell32.lib. I didnt realize this thread was 5 years old.... opps.
  • HolyBlackCat
    HolyBlackCat almost 4 years
    Generally bumping old threads is ok, but linking Shell32.lib probably won't solve the problem described by OP (it matters what symbol exactly is "unresolved").
  • The XGood
    The XGood over 3 years
    @HolyBlackCat It fixes an error with an identical output, without building the library, so it seems functional.
  • TechNyquist
    TechNyquist about 2 years
    Wrapped in extern "C" worked.
  • Juan
    Juan about 2 years
    I work on with Visual Studio 2022 and I recovered a project created on Visual Studio 2013. I had this problem and the solution by adding the code "FILE _iob[]...." work for me to resolve problem with __iob_func. I had another problem with __vsnprintf wich resolved by adding "legacy_stdio_definitions.lib" in link editor>entries>additional dependencies.