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

128

有人可以解释一下什么是

__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

我可以肯定地说问题不在链接错误上。我已经正确地链接了一切,但出于某种原因它无法编译。

我正在尝试使用SDL2。

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

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


1
请问您能否通过展示您的链接器设置来证明这一点? - πάντα ῥεῖ
@πάνταῥεῖ,我已经在输入链接器设置中链接了SDL2.lib和SDL2main.lib,并确保目录指向正确的位置。 - RockFrenzy
1
可能是error LNK2001 __imp_fprintf Visual Studio 2015 RC的重复问题。 - dewaffled
20个回答

143

我终于弄清楚为什么会发生这种情况了!

在 Visual Studio 2015 中,stdin、stderr 和 stdout 定义如下:

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

但是以前,它们的定义如下:

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

现在__iob_func不再被定义,这会导致使用先前版本的Visual Studio编译的.lib文件时出现链接错误。

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

关于其他与stdio函数相关的链接错误(在我的情况下是sprintf()),您可以将 legacy_stdio_definitions.lib 添加到链接器选项中。


1
感谢你找到了这个问题。如果我没记错的话,{*stdin,*stdout,*stderr} 的问题可能是不同的编译单元可能会有它们自己的 stdin 的副本,这就是为什么这些函数被直接调用的原因。 - Steven R. Loomis
3
那对我也有帮助,只是提醒在声明/定义中使用extern "C" - Vargas
4
有人能否写出替换函数应该长成什么样子?我尝试了不同的变体,但一直出现编译错误。谢谢。 - Milan Babuškov
61
外部 "C" { FILE __iob_func[3] = { *stdin, *stdout, *stderr }; }:这段代码定义了一个名为 "__iob_func" 的数组,其中包含三个指向 FILE 结构体的指针:stdin、stdout 和 stderr。 - PoL0
2
上面的iob_func定义不起作用,请参考MarkH的答案获取正确的定义。(你不能仅将函数定义为数组并期望调用能够正常工作。) - Hans Olsson
显示剩余3条评论

66

对于米兰·巴布什科夫来说,这正是替换函数应该看起来的样子 :-)

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

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

5
只需添加一个#ifdef针对MSVC,以及针对MSVC版本<2015的#ifdef即可。 - paulm
1
正如MarkH在另一个看起来正确的答案中指出的那样,但这并不起作用。 - Hans Olsson
4
@paulm 我想你的意思是 #if defined(_MSC_VER) && (_MSC_VER >= 1900) - Jesse Chisholm
@JesseChisholm 可能吧,这取决于它是否也适用于所有已知的未来版本的 MSVC ;) - paulm
我使用Visual Studio 2022工作,恢复了一个在Visual Studio 2013创建的项目。我曾遇到过这个问题,通过添加代码“**FILE _iob[]....**”解决了__iob_func的问题。我还有另一个问题,即__vsnprintf问题,通过在链接编辑器>条目>附加依赖项中添加“legacy_stdio_definitions.lib”解决了该问题。 - Juan

51

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

printf和scanf函数系列现在被定义为内联函数。

所有printf和scanf函数的定义都已经被移动到stdio.h, conio.h和其他CRT头文件中进行内联处理。 对于那些本地声明这些函数但未包含适当的CRT头文件的程序,这是一个破坏性的变更,会导致链接器错误(LNK2019,未解析的外部符号)。如果可能的话,您应该更新代码以包括CRT头文件(即加入#include)和内联函数,但如果不想修改您的代码以包括这些头文件,一种可行的解决方案是将一个附加库添加到链接器输入中:legacy_stdio_definitions.lib

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

如果您的项目链接了使用Visual C++ 2015之前版本编译的静态库,则链接器可能会报告未解析的外部符号。这些错误可能引用了中的内部stdio定义,例如_iob_iob_func或特定stdio函数的相关导入,形式为__imp_*。Microsoft建议在升级项目时使用最新版本的Visual C++编译器和库重新编译所有静态库。如果该库是第三方库而没有可用的源代码,则应该要求第三方提供更新的二进制文件,或者将您对该库的使用封装到一个单独的DLL中,并使用旧版的Visual C++编译器和库进行编译。


11
还有一个#pragma comment(lib, "legacy_stdio_definitions.lib") - 但这并不能解决__imp___iob_func的问题 - 是否也有一个用于此的旧版库? - bytecode77
@bytecode77你还记得你是如何解决这个问题的吗?我遇到了一个类似的错误,是关于"__imp____acrt_iob_func"的。提前谢谢! - Marisol

33

如上所述,正确的答案是使用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])

所以我们可以看到,这个函数的工作是返回一个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))

因此,替换函数的方法已经改变,现在返回文件句柄而不是文件对象数组的地址,宏也已经更改为简单地调用函数并传入标识号码。但是,为什么他们/我们不能提供兼容的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;

现在是VS2015文件定义:

typedef struct _iobuf
{
    void* _Placeholder;
} FILE;

所以问题的症结在于:结构已经改变了形状。现有的引用__iob_func的编译代码依赖于返回的数据既是可以索引的数组,而且在这个数组中元素之间的距离是相同的。
如上面答案中提到的可能的解决方案沿着这些线路将无法工作(如果被调用),原因有几个。
FILE _iob[] = {*stdin, *stdout, *stderr};

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

_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;
}

1
正确答案是这个,最简单的修复方法是升级项目到VS2015,然后编译。 - Akumaburn
4
我需要将许多项目(包括C++ 和 C# 项目)从Visual Studio 2013升级到使用 Visual Studio 2015 Update 3。在构建C++项目时,我想保留VC100(Visual Studio 2010 C++编译器),但是我遇到了与上述相同的错误。我通过将 legacy_stdio_definitions.lib 添加到链接器来修复了 _imp__fprintf 问题。 那我如何修复 _imp____iob_func 的问题呢? - Mohamed BOUZIDI
在回答我之前的问题之前,使用msbuild 14和IntelCompiler 2016和VC100编译C ++项目时出现这些错误是否正常? - Mohamed BOUZIDI
2
我该如何进行x64编译? - athos

28

我在VS2015中遇到了同样的问题。我通过在VS2015中编译SDL2源代码来解决它。

  1. 访问http://libsdl.org/download-2.0.php并下载SDL 2源代码。
  2. VS2015中打开SDL_VS2013.sln。您将被要求转换项目。请执行此操作。
  3. 编译SDL2项目。
  4. 编译SDL2main项目。
  5. 在VS2015中的SDL 2项目中使用新生成的输出文件SDL2main.lib、SDL2.lib和SDL2.dll。

5
顺便说一下,构建SDL 2.0.3需要安装2010年6月的DirectX SDK。 - Joe
2
对我有用,谢谢!但是我只需要编译SDL2main并复制SDL2main.lib - kgwong

10

我不知道为什么,但是:

#ifdef main
#undef main
#endif
在主函数前的includes后添加这段代码应该会解决这个问题,这是我的经验之谈。

1
好的,试着做之前我大声地对自己说,我有点怀疑这会不会起作用,但它完全奏效了......你能解释一下为什么会这样吗? - Trevor Hart
1
@TrevorHart 我认为它取消定义了一个“有缺陷”的SDL主要包含引用到“未定义”的缓冲区,如果你的使用了你的缓冲区,那么它就可以很好地工作。 - The XGood
2
这是一种可怕的不良实践,但只有三行代码,它能够工作并且让我避免了深入构建SDL的兔子洞...做得好。 - Cheezmeister
1
@Cheezmeister 坏习惯和hack技巧在所有事情中经常是必需的。尤其是那些本不需要它们的事情。 - The XGood
这可以解决一个与SDL相关的不同错误,但不能解决OP正在处理的那个。 - HolyBlackCat
@HolyBlackCat 它修复了一个相同输出的错误,而不需要构建库,因此它似乎是有效的。 - The XGood

8

7
我的建议是不要(试图)实现__iob_func。
在修复这些错误时: libpngd.v110.lib(pngrutil.obj):error LNK2001:未解析的外部符号___iob_func curllib.v110.lib(mprintf.obj):error LNK2001:未解析的外部符号___iob_func
我尝试了其他答案的解决方案,但最终返回一个FILE* C数组与Windows内部IOB结构的数组不匹配。 @Volker是正确的,它永远不会适用于stdin、stdout或stderr中的多个之一。
如果库实际上使用其中一个流,它将崩溃。只要您的程序不使lib使用它们,您就永远不会知道。例如,当CRC在PNG的元数据中不匹配时,png_default_error会写入stderr。(通常不是一个值得崩溃的问题)
结论:如果VS2012(Platform Toolset v110/v110_xp)和VS2015+库使用标准输入、标准输出和/或标准错误流,则无法混合使用。
解决方案:使用当前版本的VS和匹配的平台工具集重新编译具有未解析符号__iob_func的库。

7
连接意味着不正常工作。深入研究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);
}

4

我使用以下函数解决了这个问题。我使用的是Visual Studio 2019。

FILE* __cdecl __iob_func(void)
{
    FILE _iob[] = { *stdin, *stdout, *stderr };
    return _iob;
}

由于stdin宏定义的函数调用," *stdin "表达式不能用于全局数组初始化器。但是,局部数组初始化器是可以的。 抱歉,我的英语很差。

使用 extern "C" 包裹后正常运行。 - Niki Romagnoli

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接