无法parsing的外部符号__imp__fprintf和__imp____iob_func,SDL2
有人能解释一下吗?
__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作为编译器。
我链接到链接器 – >input – >附加依赖项中的SDL2.lib和SDL2main.lib,并确保VC ++目录是正确的。
我终于明白了为什么会发生这种事情!
在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添加到您的链接器选项。
对米兰Babuškov,国际海事组织,这正是替代function应该看起来像:-)
FILE _iob[] = {*stdin, *stdout, *stderr}; extern "C" FILE * __cdecl __iob_func(void) { return _iob; }
我在VS2015有同样的问题。 我通过在VS2015中编译SDL2源来解决这个问题。
- 转到http://libsdl.org/download-2.0.php并下载SDL 2源代码。
- 在VS2015中打开SDL_VS2013.sln 。 您将被要求转换项目。 这样做。
- 编译SDL2项目。
- 编译SDL2main项目。
- 在VS2015的SDL 2项目中使用新生成的输出文件SDL2main.lib,SDL2.lib和SDL2.dll。
Microsoft对此有一个特别注意( https://msdn.microsoft.com/en-us/library/bb531344.aspx#BK_CRT ):
printf和scanf函数系列现在是内联定义的。
所有printf和scanf函数的定义已经被内联到stdio.h , conio.h和其他CRT头文件中。 这是一个突破的变化,导致一个链接器错误(LNK2019,未parsing的外部符号)的任何程序在本地声明这些function,而不包括适当的CRT标题。 如果可能的话,你应该更新代码来包含CRT头文件(也就是添加#include)和内联函数,但是如果你不想修改你的代码来包含这些头文件,另一种解决方法是添加额外的库链接器input, legacy_stdio_definitions.lib 。
要将此库添加到IDE中的链接器input中,请打开项目节点的上下文菜单,select“属性”,然后在“项目属性”对话框中select“链接器”,然后编辑链接器input以将legacy_stdio_definitions.lib添加到分号 – 分离列表。
如果您的项目与使用早于2015年的Visual C ++版本编译的静态库链接,则链接器可能会报告未parsing的外部符号。 这些错误可能会以__imp_ *的forms引用_iob , _iob_func或某些stdio函数的相关导入的内部stdio定义。 Microsoftbuild议您在升级项目时使用最新版本的Visual C ++编译器和库重新编译所有静态库。 如果库是不可用的第三方库,则应该从第三方请求更新的二进制文件,或者将该库的用法封装到单独的DLL中,并使用旧版本的Visual C ++编译器进行编译和图书馆。
正如上面所回答的,正确的答案是用VS2015编译所有的东西,但是为了下面的分析,我感兴趣的是这个问题。
这个符号似乎并没有在Microsoft提供的任何静态库中定义为VS2015的一部分,这是所有其他的特殊情况。 为了find原因,我们需要看看这个函数的声明,更重要的是看它是如何被使用的。
以下是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是存储重要状态好东西的底层不透明数据结构)。 这个函数的用户是三个macrosstdin,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))
因此,replace函数的方法已经改变,现在返回文件句柄而不是文件对象数组的地址,并且macros已经改变为简单地调用传递给标识号的函数。
那么为什么他们/我们不能提供兼容的API呢? 有两个关键的规则,微软无法通过__iob_func实现它们的原始实现:
- 必须有一个三个FILE结构的数组,可以像以前一样进行索引。
- FILE的结构布局不能改变。
以上两种情况的任何变化都意味着现有的已编译的代码会被调用,如果这个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;
而现在的VS2015 FILE定义:
typedef struct _iobuf { void* _Placeholder; } FILE;
所以关键在于:结构已经改变了。 引用__iob_func的现有编译代码依赖于这样一个事实:返回的数据既是一个可以索引的数组,也是在该数组中,元素间距相同。
上面这些答案中提到的可能解决scheme将不起作用(如果调用),原因如下:
FILE _iob[] = {*stdin, *stdout, *stderr}; extern "C" FILE * __cdecl __iob_func(void) { return _iob; }
FILE数组_iob将用VS2015进行编译,所以它将作为包含void *的结构块进行布局。 假设32位alignment,这些元素将相距4个字节。 因此,_iob [0]位于偏移量0,_iob [1]位于偏移量4,_iob [2]位于偏移量8处。调用代码将会使FILE更长,在我的系统上以32字节alignment,它将采用返回的数组的地址,并添加0字节到元素零(这是一个好的),但对于_iob [1]它会推断它需要添加32个字节和_iob [2]它会推断它需要添加64个字节(因为这是VS2008头文件的样子)。 事实上VS2008的反汇编代码certificate了这一点。
上述解决scheme的第二个问题是它复制 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; }
这个问题的更新解决scheme:使用更新的sdl库
“ https://buildbot.libsdl.org/sdl-builds/sdl-visualstudio/?C=M;O=D ”
他们似乎已经解决了这个问题,虽然它只是32位库(我认为)。
链接意味着不能正常工作。 挖掘到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); }
我不知道为什么,但:
#ifdef main #undef main #endif
包括之后,但你的主要之前,应该从我的经验来解决它。
为VS2015项目的库使用预编译的SDL2main.lib和SDL.lib: https ://buildbot.libsdl.org/sdl-builds/sdl-visualstudio/sdl-visualstudio-2225.zip
这可能发生在链接到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。
我设法解决这个问题。
错误的来源是这行代码,可以在SDLmain源代码中find。
fprintf(stderr, "%s: %s\n", title, message);
所以我所做的就是编辑该行的SDLmain中的源代码:
fprintf("%s: %s\n", title, message);
然后我构build了SDLmain,并用新build和编辑的SDL2库目录复制并replace了旧的SDLmain.lib。
然后,当我用SDL2运行我的程序时,没有出现错误消息,代码运行顺畅。
我不知道这会不会在晚些时候咬我,但是这样一切都变好了。