【问题标题】:unresolved external symbol __imp__fprintf and __imp____iob_func, SDL2未解析的外部符号 __imp__fprintf 和 __imp____iob_func,SDL2
【发布时间】:2023-12-22 03:17:02
【问题描述】:

谁能解释一下

__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 作为编译器。

我已在 Linker -> Input -> Additional Dependencies 中链接到 SDL2.lib 和 SDL2main.lib,并确保 VC++ 目录正确。

【问题讨论】:

  • 您能否通过显示您的链接器设置来证明这一点。
  • @πάνταῥεῖ,我已在输入链接器设置中链接到 SDL2.lib 和 SDL2main.lib,并确保目录指向正确的目录。

标签: c++ sdl-2 visual-studio-2015 unresolved-external


【解决方案1】:

我终于知道为什么会这样了!

在 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 添加到您的链接器选项中。

【讨论】:

  • 感谢您跟踪此问题。 IIRC {*stdin,*stdout,*stderr} 的问题可能是不同的编译单元可能有自己的标准输入副本,这就是直接调用这些函数的原因。
  • 这也为我解决了,只是提醒在声明/定义中使用extern "C"
  • 有人能准确地写出替换函数应该是什么样子吗?我尝试了不同的变体,但我不断收到编译错误。谢谢。
  • extern "C" { FILE __iob_func[3] = { *stdin,*stdout,*stderr }; }
  • 上面的 iob_func 定义不起作用,请参阅 MarkH 的答案以获得正确的定义。 (您不能只将函数定义为数组并期望调用起作用。)
【解决方案2】:

对于 IMO 的 Milan Babuškov,这正是替换函数的样子:-)

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

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

【讨论】:

  • 只是缺少一个用于 MSVC 和 MSVC 版本 的 #ifdef
  • 正如 MarkH 在另一个看起来正确但不起作用的答案中指出的那样。
  • @paulm 我想你的意思是#if defined(_MSC_VER) && (_MSC_VER >= 1900)
  • @JesseChisholm 也许,取决于这是否也适用于所有已知的 MSVC 未来版本;)
【解决方案3】:

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

printf 和 scanf 系列函数现在是内联定义的。

所有 printf 和 scanf 函数的定义已经 内嵌到 stdio.hconio.h 和其他 CRT 标头中。 这是导致链接器错误的重大更改(LNK2019, 未解析的外部符号)对于声明这些的任何程序 在不包括适当的 CRT 标头的情况下在本地运行。如果 可能,您应该更新代码以包含 CRT 标头(即 是,添加 #include )和内联函数,但如果你这样做 不想修改您的代码以包含这些头文件, 替代解决方案是向链接器添加一个额外的库 输入,legacy_stdio_definitions.lib

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

如果您的项目链接到使用 早于 2015 年的 Visual C++ 版本,链接器可能会报告 未解析的外部符号。这些错误可能涉及内部 _iob_iob_func 或相关导入的 stdio 定义 __imp_* 形式的某些 stdio 函数。微软 建议您使用最新的重新编译所有静态库 升级时 Visual C++ 编译器和库的版本 项目。如果库是第三方库,其来源是 不可用,您应该从 第三方或将您对该库的使用封装到单独的 使用旧版 Visual C++ 编译器编译的 DLL 和图书馆。

【讨论】:

  • #pragma comment(lib, "legacy_stdio_definitions.lib") - 但这不能解决__imp___iob_func - 是否也有旧版库?
【解决方案4】:

如上回答,正确的答案是用VS2015编译一切,但出于兴趣,以下是我对问题的分析。

这个符号似乎没有在微软提供的任何静态库中定义为 VS2015 的一部分,这是相当奇特的,因为所有其他的都是。要找出原因,我们需要查看该函数的声明,更重要的是查看它的使用方式。

这是来自 Visual Studio 2008 标头的 sn-p:

_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 的原始实现而言,Microsoft 不能违反两个关键规则:

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

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 不同的底层结构。如果结构仅包含指针,这可能会起作用,但这是一个很大的风险。无论如何,第一个问题使这无关紧要。

我能想到的唯一技巧是 __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;
}

【讨论】:

  • 正确答案是这个,最简单的修复方法是升级项目到VS2015然后编译。
  • 就我而言,我需要从 Visual Studio 2013 升级许多项目(C++ 和 C# 项目)以使用 Visual Studio 2015 Update 3。我想在构建时保留 VC100(Visual Studio 2010 C++ 编译器) C++ 项目,但我有与上述相同的错误。我通过将 legacy_stdio_definitions.lib 添加到链接器来修复 imp_fprintf。我该如何解决 _imp____iob_func
  • 在回答我之前的问题之前,使用msbuild 14和IntelCompiler 2016和VC100编译C++项目时出现这些错误是否正常?
  • 我可以为 x64 编译做什么?
【解决方案5】:

我在 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。

【讨论】:

  • 顺便说一句,构建 SDL 2.0.3 需要安装 2010 年 6 月的 DirectX SDK。
  • 为我工作,谢谢!!但我只需要编译SDL2main并复制SDL2main.lib
【解决方案6】:

我不知道为什么,但是:

#ifdef main
#undef main
#endif

根据我的经验,在包含之后但在你的主要应该修复它之前。

【讨论】:

  • ....好吧....所以在尝试这个之前,我对自己说,我不知何故怀疑这会起作用,但它完全起作用了......你能解释一下为什么会这样...?
  • @TrevorHart 我相信它取消了对“未定义”缓冲区的“错误”SDL 主要包括对“未定义”缓冲区的引用,如果您的缓冲区使用您的缓冲区,那么它工作得很好.
  • 这是非常糟糕的做法,但它只有 3 行代码,而且很有效,而且它让我免于陷入构建 SDL 的困境……做得很好。
  • @Cheezmeister 不好的做法和黑客通常需要一切。尤其是那些不应该需要它们的东西。
  • 这可以修复 不同 SDL 相关的错误,但不是 OP 正在处理的错误。
【解决方案7】:

此问题的更新解决方案:使用更新的 sdl 库

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

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

【讨论】:

    【解决方案8】:

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

    【讨论】:

      【解决方案9】:

      我的建议是不要(尝试)实现 __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 是对的,它永远不会适用于 stdinstdoutstderr 中的一个以上。

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

      结论:如果 VS2012 (Platform Toolset v110/v110_xp) 和 VS2015+ 库使用标准输入、标准输出和/或标准错误,则不能混合使用。

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

      【讨论】:

        【解决方案10】:

        为您的 VS2015 项目的库使用预编译的 SDL2main.lib 和 SDL.lib: https://buildbot.libsdl.org/sdl-builds/sdl-visualstudio/sdl-visualstudio-2225.zip

        【讨论】:

          【解决方案11】:

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

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

          因为标准输入宏定义的函数调用,“*stdin”表达式不能使用全局数组初始值设定项。但是本地数组初始值是可能的。 对不起,我的英语很差。

          【讨论】:

            【解决方案12】:

            对于仍在寻找上述技巧不起作用的答案的任何人。静态链接是解决这个问题的方法。如下更改您的运行时库设置

            Project properties --&gt; C/C++ --&gt; Code generation --&gt; Runtime Library --&gt; Multi-threaded Debug (/MTd) instead of /MDd

            这里是关于这个解决方案的讨论:https://social.msdn.microsoft.com/Forums/vstudio/en-US/4a1c9610-fa41-45f6-ad39-c9f6795be6f2/msvcrt-iob-disappeared?forum=vclanguage

            【讨论】:

            【解决方案13】:

            我设法解决了这个问题。

            错误的来源是这行代码,可以在 SDLmain 源代码中找到。

            fprintf(stderr, "%s: %s\n", title, message);
            

            所以我所做的就是在该行的 SDLmain 中编辑源代码:

            fprintf("%s: %s\n", title, message);
            

            然后我构建了 SDLmain 并复制并用新构建和编辑的 SDL2 库目录中的旧 SDLmain.lib 替换。

            然后,当我使用 SDL2 运行我的程序时,没有出现错误消息并且代码运行顺利。

            我不知道这会不会以后会咬我,但一切都很好。

            【讨论】:

            • 您的更改本身就是一个错误,不会解决您问题中描述的问题。您不再遇到链接器错误只是一个巧合,这可能仅仅是您如何重建库的结果。
            • @RossRidge,哦,是的,可能就是这样。嗯嗯。
            【解决方案14】:

            当您链接到 msvcrt.dll 而不是 msvcr10.dll(或类似)时,可能会发生这种情况,这是一个不错的计划。因为它可以让您腾出时间在最终软件包中重新分发 Visual Studio 的运行时库。

            该解决方法对我有帮助(在 Visual Studio 2008 中):

            #if _MSC_VER >= 1400
            #undef stdin
            #undef stdout
            #undef stderr
            extern "C" _CRTIMP extern FILE _iob[];
            #define stdin   _iob
            #define stdout  (_iob+1)
            #define stderr  (_iob+2)
            #endif
            

            Visual Studio 6 及其编译器不需要此 sn-p。因此#ifdef。

            【讨论】:

              【解决方案15】:

              为了给这个已经很丰富的线程带来更多混乱,我碰巧在 fprintf 上碰到了同一个未解决的外部

              main.obj : error LNK2019: unresolved external symbol __imp__fprintf referenced in function _GenerateInfoFile
              

              即使在我的情况下,它是在一个完全不同的环境中:在 Visual Studio 2005 (Visual Studio 8.0) 下,并且错误发生在我自己的代码中(与我正在编译的代码相同),而不是第三方。

              这个错误是由我的编译器标志中的 /MD 选项触发的。切换到 /MT 消除了这个问题。这很奇怪,因为通常静态链接 (MT) 会引发比动态链接 (MD) 更多的问题……但以防万一它服务于其他问题,我把它放在那里。

              【讨论】:

                【解决方案16】:

                在我的情况下,这个错误来自我尝试删除依赖于 MSVC 版本的运行时库 DLL(msvcr10.dll 左右)和/或删除静态运行时库的尝试,以从我的可执行文件中删除多余的脂肪。

                所以我使用 /NODEFAULTLIB 链接器开关、我自制的“msvcrt-light.lib”(需要时用 Google 搜索)和 mainCRTStartup() / WinMainCRTStartup() 条目。

                恕我直言,自 Visual Studio 2015 以来,我坚持使用较旧的编译器。

                然而,定义符号 _NO_CRT_STDIO_INLINE 消除了所有麻烦,一个简单的“Hello World”应用程序又是 3 KB 小,并且不依赖于异常的 DLL。 在 Visual Studio 2017 中测试。

                【讨论】:

                  【解决方案17】:

                  也许这对你有帮助。我将 Shell32.lib 添加到我的链接器 --> 输入 --> 附加依赖项中,它停止了这个错误。我是从这篇文章中了解到的:https://discourse.libsdl.org/t/windows-build-fails-with-missing-symbol-imp-commandlinetoargvw/27256/3

                  【讨论】:

                  • 您是否遇到与 OP 或 unresolved external symbols 完全相同的错误?
                  • 通常无法解析的外部符号。在我添加 shell32.lib 之前没有任何效果。我没有意识到这个线程是 5 岁.... opps.
                  • 一般来说碰撞旧线程是可以的,但链接Shell32.lib 可能不会解决OP 描述的问题(重要的是究竟什么符号是“未解决”)。
                  【解决方案18】:

                  将此代码粘贴到您的任何源文件中并重新构建。 为我工作!

                   #include <stdio.h>
                  
                  FILE _iob[3];
                  
                  FILE* __cdecl __iob_func(void)
                  {
                  
                     _iob[0] = *stdin;
                  
                     _iob[0] = *stdout;
                  
                     _iob[0] = *stderr;
                  
                     return _iob;
                  
                  }
                  

                  【讨论】:

                  • 你应该用```添加格式和一些解释
                  • 虽然此代码可以解决问题,including an explanation 说明如何以及为什么解决问题将真正有助于提高您的帖子质量,并可能导致更多的赞成票。请记住,您正在为将来的读者回答问题,而不仅仅是现在提问的人。请edit您的回答添加解释并说明适用的限制和假设。
                  最近更新 更多