未解析的外部符号__imp__fprintf和__imp____iob_func,SDL2

Roc*_*nzy 102 c++ unresolved-external sdl-2 visual-studio-2015

有人可以解释一下

__imp__fprintf

__imp____iob_func

未解决的外部手段?

因为我在尝试编译时遇到这些错误:

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
Run Code Online (Sandbox Code Playgroud)

我已经可以说问题不在于连接错误.我已经正确地链接了所有内容,但由于某种原因它无法编译.

我正在尝试使用SDL2.

我正在使用Visual Studio 2015作为编译器.

我已链接到链接器中的SDL2.lib和SDL2main.lib - >输入 - >附加依赖项,我确保VC++目录是正确的.

小智 112

我终于弄明白了为什么会这样!

在visual studio 2015中,stdin,stderr,stdout定义如下:

#define stdin  (__acrt_iob_func(0))
#define stdout (__acrt_iob_func(1))
#define stderr (__acrt_iob_func(2))
Run Code Online (Sandbox Code Playgroud)

但之前,它们被定义为:

#define stdin  (&__iob_func()[0])
#define stdout (&__iob_func()[1])
#define stderr (&__iob_func()[2])
Run Code Online (Sandbox Code Playgroud)

所以现在__iob_func不再定义,导致在使用以前版本的visual studio编译的.lib文件时出现链接错误.

要解决此问题,您可以尝试定义__iob_func()自己应该返回包含的数组{*stdin,*stdout,*stderr}.

关于stdio函数的其他链接错误(在我的例子中sprintf()),您可以将legacy_stdio_definitions.lib添加到链接器选项.

  • `extern"C"{FILE __iob_func [3] = {*stdin,*stdout,*stderr}; }` (49认同)
  • 有人可以准确地写下替换功能应该是什么样的吗?我尝试了不同的变种,我不断收到编译错误.谢谢. (4认同)
  • 这也解决了我,只是提醒在声明/定义中使用`extern"C"`. (2认同)
  • 上面的 iob_func 定义不起作用,请参阅 MarkH 的答案以获得正确的定义。(您不能只将函数定义为数组并期望调用起作用。) (2认同)

小智 56

对IMO的米兰巴布什科夫来说,这正是替换功能的样子:-)

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

extern "C" FILE * __cdecl __iob_func(void)
{
    return _iob;
}
Run Code Online (Sandbox Code Playgroud)

  • 缺少MSVC的#ifdef和MSVC版本<2015 (5认同)
  • @paulm我认为你的意思是`#if defined(_MSC_VER)&&(_ MSC_VER> = 1900)`. (4认同)

小智 39

Microsoft对此有一个特别说明(https://msdn.microsoft.com/en-us/library/bb531344.aspx#BK_CRT):

printf和scanf系列函数现在以内联方式定义.

所有printf和scanf函数的定义都已内联移入stdio.h,conio.h和其他CRT头文件中.这是一个重大更改,导致链接器错误(LNK2019,未解析的外部符号),用于在本地声明这些函数但不包含相应CRT头的任何程序.如果可能,您应该更新代码以包含CRT标头(即添加#include)和内联函数,但如果您不想修改代码以包含这些头文件,则另一种解决方案是添加额外的库到您的链接器输入,legacy_stdio_definitions.lib.

要将此库添加到IDE中的链接器输入,请打开项目节点的上下文菜单,选择"属性",然后在"项目属性"对话框中选择"链接器",并编辑链接器输入以将legacy_stdio_definitions.lib添加到分号 - 分隔列表.

如果您的项目链接到使用早于2015年的Visual C++版本编译的静态库,则链接器可能会报告未解析的外部符号.这些错误可能以__imp_*的形式引用某些stdio函数的_iob,_iob_func或相关导入的内部stdio定义.Microsoft建议您在升级项目时使用最新版本的Visual C++编译器和库重新编译所有静态库.如果库是没有源的第三方库,您应该从第三方请求更新的二进制文件,或者将您对该库的使用封装到使用旧版Visual C++编译器编译的单独DLL中和图书馆.

  • 或者`#pragma comment(lib,"legacy_stdio_definitions.lib")` - 但这不能修复`__imp ___ iob_func` - 这也有遗留的lib吗? (7认同)

小智 28

I had the same issue in VS2015. I have solved it by compiling the SDL2 sources in VS2015.

  1. Go to http://libsdl.org/download-2.0.php and download SDL 2 source code.
  2. Open SDL_VS2013.sln in VS2015. You will be asked to convert the projects. Do it.
  3. Compile SDL2 project.
  4. Compile SDL2main project.
  5. Use the new generated output files SDL2main.lib, SDL2.lib and SDL2.dll in your SDL 2 project in VS2015.

  • 顺便说一下,构建SDL 2.0.3需要安装2010年6月的DirectX SDK. (4认同)
  • 为我工作,谢谢!!但我只需要编译`SDL2main`并复制`SDL2main.lib` (2认同)

小智 25

如上所述,正确的答案是使用VS2015编译所有内容,但为了感兴趣,以下是我对问题的分析.

这个符号似乎没有在Microsoft提供的任何静态库中定义,作为VS2015的一部分,这是非常特殊的,因为所有其他的都是.为了发现原因,我们需要查看该函数的声明,更重要的是,它是如何使用的.

这是Visual Studio 2008标头的一个片段:

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

因此我们可以看到函数的工作是返回FILE对象数组的开始(不是句柄,"FILE*"是句柄,FILE是存储重要状态好东西的底层不透明数据结构).该函数的用户是三个宏stdin,stdout和stderr,它们用于各种fscanf,fprintf样式调用.

现在让我们来看看Visual Studio 2015如何定义相同的东西:

_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))
Run Code Online (Sandbox Code Playgroud)

因此,替换函数的方法已更改为现在返回文件句柄而不是文件对象数组的地址,并且宏已更改为简单地调用传递标识号的函数.

那么他/我们为什么不能提供兼容的API呢?微软通过__iob_func在原始实现方面不能违反两条关键规则:

  1. 必须有一个由三个FILE结构组成的数组,这些结构可以像以前一样被索引.
  2. FILE的结构布局不能改变.

如果调用该API,则上述任何一项中的任何更改都将意味着与此相关联的现有编译代码将严重错误.

我们来看看FILE是如何定义的.

首先是VS2008 FILE定义:

struct _iobuf {
        char *_ptr;
        int   _cnt;
        char *_base;
        int   _flag;
        int   _file;
        int   _charbuf;
        int   _bufsiz;
        char *_tmpfname;
        };
typedef struct _iobuf FILE;
Run Code Online (Sandbox Code Playgroud)

现在VS2015 FILE定义:

typedef struct _iobuf
{
    void* _Placeholder;
} FILE;
Run Code Online (Sandbox Code Playgroud)

所以有它的关键:结构改变了形状.引用__iob_func的现有编译代码依赖于返回的数据既是可以索引的数组又在该数组中元素相同距离的事实.

上述答案中提到的可能的解决方案在这些方面不起作用(如果被调用),原因如下:

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

extern "C" FILE * __cdecl __iob_func(void)
{
    return _iob;
}
Run Code Online (Sandbox Code Playgroud)

FILE数组_iob将使用VS2015进行编译,因此它将被布局为包含void*的结构块.假设32位对齐,这些元素将相隔4个字节.因此_iob [0]位于偏移0,_ iob [1]位于偏移4处,_ iob [2]位于偏移8.调用代码将改为期望FILE更长,在我的系统上以32字节对齐,所以它将获取返回数组的地址并添加0个字节以获取元素零(这一个是可以的),但是对于_iob [1]它将推断它需要添加32个字节而对于_iob [2]它将推导出它需要添加64字节(因为这是它在VS2008标头中的样子).事实上,VS2008的反汇编代码证明了这一点.

上述解决方案的第二个问题是它复制了FILE结构(*stdin)的内容,而不是FILE*句柄.所以任何VS2008代码都会关注VS2015的不同底层结构.如果结构只包含指针,这可能会有效,但这是一个很大的风险.无论如何,第一个问题使这无关紧要.

我能够想到的唯一一个hack就是__iob_func遍历调用堆栈以找出他们正在查找的实际文件句柄(基于添加到返回地址的偏移量)并返回一个计算值以便它给出了正确的答案.这听起来有点疯狂,但下面列出了仅适用于x86(不是x64)的原型供您娱乐.它在我的实验中运行正常,但您的里程可能会有所不同 - 不建议用于生产!

#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;
}
Run Code Online (Sandbox Code Playgroud)

  • 在我的例子中,我需要从visual studio 2013升级许多项目(C++和C#项目)以使用Visual Studio 2015 Update 3.我想在构建C++项目时保留VC100(Visual Studio 2010 C++编译器)但我有相同的错误如上.我通过向链接器添加**legacy_stdio_definitions.lib**来修复**_ imp__fprintf**.我怎样才能修复**_ imp ____ iob_func**? (2认同)
  • 我可以为 x64 编译做什么? (2认同)

小智 8

这个问题的最新解决方案:使用更新的sdl库

" https://buildbot.libsdl.org/sdl-builds/sdl-visualstudio/?C=M;O=D "

他们似乎已经解决了这个问题,虽然它只是32位库(我认为).


The*_*ood 8

我不知道为什么但是:

#ifdef main
#undef main
#endif
Run Code Online (Sandbox Code Playgroud)

在包括之后,但在你的主要应该从我的经验中解决它.

  • 这是可怕的不良实践技巧,但是它只有3条线,而且有效,它使我不必再费劲地构建SDL了……很好。 (2认同)

小智 6

链接意味着不能正常工作.深入研究VS2012和VS2015的stdio.h,以下内容对我有用.唉,你必须决定它是否适用于{stdin,stdout,stderr}之一,永远不会超过一个.

extern "C" FILE* __cdecl __iob_func()
{
    struct _iobuf_VS2012 { // ...\Microsoft Visual Studio 11.0\VC\include\stdio.h #56
        char *_ptr;
        int   _cnt;
        char *_base;
        int   _flag;
        int   _file;
        int   _charbuf;
        int   _bufsiz;
        char *_tmpfname; };
    // VS2015 has only FILE = struct {void*}

    int const count = sizeof(_iobuf_VS2012) / sizeof(FILE);

    //// stdout
    //return (FILE*)(&(__acrt_iob_func(1)->_Placeholder) - count);

    // stderr
    return (FILE*)(&(__acrt_iob_func(2)->_Placeholder) - 2 * count);
}
Run Code Online (Sandbox Code Playgroud)


Luc*_*oom 6

我的建议是不要(尝试)实现__iob_func.

在修复这些错误时:

libpngd.v110.lib(pngrutil.obj) : error LNK2001: unresolved external symbol ___iob_func curllib.v110.lib(mprintf.obj) : error LNK2001: unresolved external symbol ___iob_func

我尝试了其他答案的解决方案,但最终,返回一个FILE*C阵列与Windows内部IOB结构的数组不匹配.@Volker是对的,它永远不会超过一个stdin,stdout或者stderr.

如果一个库实际使用其中一个流,它将崩溃.只要您的程序不会导致lib使用它们,您就永远不会知道.例如,png_default_error写入stderrCRC在PNG元数据中不匹配的时间.(通常不是一个值得崩溃的问题)

结论:如果使用stdin,stdout和/或stderr,则无法混合VS2012(Platform Toolset v110/v110_xp)和VS2015 +库.

解决方案:__iob_func使用当前版本的VS和匹配的平台工具集重新编译具有未解析符号的库.