unresolved external symbol __imp__fprintf and __imp____iob_func, SDL2

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.

151565 次浏览

我设法解决了这个问题。

The source of the error was this line of code, that can be found in the SDLmain source code.

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

所以我也用 SDLmain 编辑了这一行的源代码:

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

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

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

I don't know if this will bite me later, but so for everything is going great.

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

在 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()) ,可以将 Heritage _ stdio _ Definition. lib添加到链接器选项中。

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

  1. Go to http://libsdl.org/download-2.0.php and download SDL 2 source code.
  2. VS2015中打开 SDL _ VS2013. sln。您将被要求转换项目。
  3. 编译 SDL2项目。
  4. 编译 SDL2主项目。
  5. 在 VS2015中的 SDL 2项目中使用新生成的输出文件 SDL2main.lib、 SDL2.lib 和 SDL2.dll。

这个问题的最新解决方案是: 使用

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

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

对于 Milan Babu kov,IMO,这正是替换函数应该看起来的样子: -)

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


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

当您链接到 msvcrt.dll 而不是 msvcr10.dll (或类似的)时,就会发生这种情况,这是一个很好的计划。因为它将使您能够在最终的软件包中重新分发 VisualStudio 的运行时库。

这个变通方法对我有帮助(在 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

VisualStudio6及其编译器不需要此代码片段。

Use the pre-compiled SDL2main.lib and SDL.lib for your VS2015 project's library : Https://buildbot.libsdl.org/sdl-builds/sdl-visualstudio/sdl-visualstudio-2225.zip

正如上面所回答的,正确的答案是使用 VS2015编译所有内容,但出于兴趣,下面是我对这个问题的分析。

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

下面是来自 VisualStudio2008标题的一个片段:

_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))

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.

那么为什么他们/我们不能提供兼容的 API 呢? 通过 _ _ iob _ func,微软不能违反以下两条关键规则:

  1. 必须有一个由三个 FILE 结构组成的数组,它们可以像前面那样以相同的方式进行索引。
  2. The structural layout of FILE cannot change.

上述任何一种情况的改变都意味着,如果调用该 API,与之相关联的现有编译代码将出现严重错误。

让我们看看如何定义 FILE。

首先是 VS2008文件定义:

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;

这就是问题的症结所在: 结构已经改变了形状。引用 _ _ iob _ func 的现有编译代码依赖于这样一个事实: 返回的数据既是可以索引的数组,而且在该数组中元素之间的距离相同。

上面的答案中提到的可能的解决方案由于以下几个原因(如果需要的话)将不起作用:

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.

上述解决方案的第二个问题是它的 副本是文件结构(* stdin)的内容,而不是文件 * 句柄。因此,任何 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;
}

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

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

Printf 和 Scanf 函数族现在是内联定义的。

所有 printf 和 Scanf 函数的定义如下 移动到 conio.h和其他 CRT 头内联。 这是导致链接器错误(LNK2019, 未解析的外部符号) 在不包括适当的 CRT 标头的情况下在本地运行 在可能的情况下,应更新代码,以包括 CRT 标头(即 is, add #include ) and the inline functions, but if you do not want to modify your code to include these header files, an 另一种解决方案是向链接器添加一个附加库 input, legacy_stdio_definitions.lib.

To add this library to your linker input in the IDE, open the context 菜单中,选择“属性”,然后在“项目”中 “属性”对话框中,选择链接器,并编辑要添加的链接器输入 legacy_stdio_definitions.lib to the semi-colon-separated list.

如果您的项目链接到使用 如果 VisualC + + 的版本早于2015年发布,链接器可能会报告一个 未解析的外部符号。这些错误可能引用内部 stdio definitions for (西班牙语), 好的, or related imports for 以 小鬼.Microsoft 的形式提供某些 stdio 函数 建议您使用最新的 升级 VisualC + + 编译器和库的版本 如果该库是第三方库,其源代码为 方法请求更新的二进制文件 第三方或将该库的使用封装到单独的 使用较早版本的 VisualC + + 编译器编译的 DLL 和图书馆。

我不知道为什么,但是:

#ifdef main
#undef main
#endif

包括之后,但之前你的主要应该修复它从我的经验。

我的建议是不要(试图)实现 _ _ iob _ func。

在修正这些错误时:

Libpngd.v110.lib (pngrutil.obj) : 错误 LNK2001: 未解析的外部符号 _ _ iob _ func Curllib.v110.lib (mprintf.obj) : 错误 LNK2001: 未解析的外部符号 _ _ iob _ func

我尝试了其他答案的解决方案,但最终,返回的 FILE* C 数组与 Windows 内部 IOB 结构的数组不匹配。@ Volker 是对的,它永远不会在超过一个 stdinstdoutstderr的情况下工作。

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

结论: 如果使用 stdin、 stdout 和/或 stderr,则不可能将 VS2012(Platform Toolset v110/v110 _ xp)和 VS2015 + 库混合使用。

Solution: Recompile your libraries that have __iob_func unresolved symbols with your current version of VS and a matching Platform Toolset.

为了给这个已经很丰富的线程带来更多的困惑,我碰巧在 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)引发的问题更多... ... 但是为了以防万一,我把它放在了那里。

在我的案例中,这个错误来自于我试图移除对 MSVC 版本依赖的运行时库 DLL (msvcr10.DLL 左右)的依赖和/或移除静态运行时库,从我的可执行文件中移除多余的脂肪。

所以我使用/NODEFAULTLIB 链接器开关,我自制的“ msvcrt-light. lib”(你需要的时候 google 一下)和 mainCRTStartup()/WinMainCRTStartup()条目。

自从 VisualStudio2015以来,它就是 IMHO,所以我坚持使用较老的编译器。

但是,定义符号 _ NO _ CRT _ STDIO _ INLINE消除了所有麻烦,一个简单的“ Hello World”应用程序也只有3KB 大小,并且不依赖于不常见的 DLL。 在 VisualStudio2017中测试。

我用以下函数解决这个问题。

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

由于 stdin 宏定义了函数调用,“ * stdin”表达式不能使用全局数组初始值设定器。但可以使用局部数组初始值设定器。 对不起,我英语不好。

对于那些仍在寻找上述技巧不起作用的答案的人来说。静态链接是解决这一问题的方法。如下所示更改运行库设置

Project properties --> C/C++ --> Code generation --> Runtime Library --> 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

将此代码粘贴到任何源文件中并重新生成。 对我有用!

 #include <stdio.h>


FILE _iob[3];


FILE* __cdecl __iob_func(void)
{


_iob[0] = *stdin;


_iob[0] = *stdout;


_iob[0] = *stderr;


return _iob;


}

也许这个能帮到你。我将 Shell32.lib 添加到我的链接器—— > Input —— > Add Dependency,它停止了这个错误。我是从这篇文章中得知的: https://discourse.libsdl.org/t/windows-build-fails-with-missing-symbol-imp-commandlinetoargvw/27256/3

我在尝试将.lib 链接到我的 vs2015项目时遇到了同样的问题(未解决的外部符号 _ _ impl _ printf)

对我来说唯一的解决办法就是 项目-> 属性-> 链接器-> 附加选项中的命令行框添加: Heritage _ stdio _ Definition. lib