未解析的外部符号__imp__fprintf和__imp ____ iob_func,SDL2


108

有人可以解释一下

__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 2015年

Answers:


123

我终于弄清楚了为什么会这样!

在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
感谢您对此进行跟踪。IIRC {* stdin,* stdout,* stderr}的问题可能是不同的编译单元可能拥有其自己的stdin副本,这就是为什么直接调用这些函数的原因。
史蒂文·R·鲁米斯

3
这也为我解决了,只是提醒您extern "C"在声明/定义中使用。
Vargas 2015年

4
有人可以确切地写出替换功能的样子吗?我尝试了不同的变体,但不断出现编译错误。谢谢。
米兰Babuškov2015年

55
extern "C" { FILE __iob_func[3] = { *stdin,*stdout,*stderr }; }
PoL0

1
上面的iob_func定义不起作用,请参阅MarkH的答案以获取正确的定义。(您不能仅将函数定义为数组并希望调用即可工作。)
Hans Olsson

59

对于IMO的米兰·巴布斯科夫(MilanBabuškov),这正是替换函数的外观:-)

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

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

5
只是缺少#ifdef用于MSVC和MSVC版本<2015
paulm

1
正如MarkH在另一个答案中指出的那样,它看起来是正确的,但无法正常工作。
汉斯·奥尔森

4
@paulm我想你的意思是#if defined(_MSC_VER) && (_MSC_VER >= 1900)
杰西·奇斯霍尔姆

@JesseChisholm可能取决于这是否也适用于MSVC的每个已知将来版本;)
paulm

42

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

现在,内联定义了printf和scanf函数系列。

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

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

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


7
或者#pragma comment(lib, "legacy_stdio_definitions.lib")-但这不能解决__imp___iob_func-是否也有旧版的库?
bytecode77

29

如上所述,正确的答案是使用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是存储重要状态信息的底层不透明数据结构)。此函数的用户是用于各种fscanf,fprintf样式调用的三个宏stdin,stdout和stderr。

现在让我们看一下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 FILE定义为:

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然后编译。
Akumaburn'5

2
就我而言,我需要从Visual Studio 2013升级许多项目(C ++和C#项目)以使用Visual Studio 2015 Update3。在构建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

1
对于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。

4
顺便说一句,构建SDL 2.0.3要求安装2010年6月DirectX SDK。
2015年

1
为我工作,谢谢!但我只需要编译SDL2main并复制了SDL2main.lib
kgwong

10

我不知道为什么,但是:

#ifdef main
#undef main
#endif

在包含之后但在您主要使用之前,应根据我的经验进行修复。


1
....好.....所以在尝试此操作之前,我对自己说出了一个声音,我以某种方式怀疑它是否会奏效,但它完全奏效了......
Trevor Hart

1
@TrevorHart我相信它未定义“错误的” SDL main包含的对“未定义的”缓冲区的引用,如果您使用您的缓冲区,那么它会很好地工作。
XGood

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

1
@Cheezmeister一切都经常需要不好的作法和hack。特别是不需要它们的东西。
XGood


7

链接意味着无法正常工作。深入研究VS2012和VS2015的stdio.h,以下对我有用。las,您必须决定它是否适用于{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);
}

7

我的建议是不要(尝试)实现__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。(通常不是值得崩溃的问题)

结论:如果它们使用stdin,stdout和/或stderr,则无法混合使用VS2012(平台工具集v110 / v110_xp)和VS2015 +库。

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


2

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


2

我通过以下功能解决了这个问题。我使用Visual Studio 2019。

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

因为stdin宏定义了函数调用,所以不能使用“ * stdin”表达式来使用全局数组初始化程序。但是本地数组初始化是可能的。对不起,我英语不好。



0

我设法解决了这个问题。

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

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

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

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

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

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

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


您的更改本身就是一个错误,不会解决您的问题中描述的问题。您不再遇到链接器错误只是一个巧合,这可能仅仅是您重建库的结果。
Ross Ridge)

@RossRidge,哦,是的。呃,好吧。
RockFrenzy

0

当您链接到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及其编译器不需要此代码段。因此,#ifdef。


0

为了给这个本已丰富的线程带来更多混乱,我碰巧碰上了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)引起的问题更多...但是为了防万一它可以为其他人服务,我把它放在了那里。


0

就我而言,此错误来自我的尝试,该尝试是删除对依赖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中测试。


-2

将此代码粘贴到您的任何源文件中,然后重新生成。为我工作!

#include stdio.h

文件_iob [3];

FILE * __cdecl __iob_func(void){

_iob [0] = * stdin;

_iob [0] = * stdout;

_iob [0] = * stderr;

返回_iob;

}


你应该用加``也有一些解释在格式化`
安东尼GAVREL

尽管这段代码可以解决问题,但包括解释如何以及为何解决该问题的说明,确实可以帮助提高您的帖子质量,并可能导致更多的投票。请记住,您将来会为读者回答问题,而不仅仅是现在问的人。请编辑您的答案以添加说明,并指出适用的限制和假设。
布赖恩
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.