有人可以解释一下
__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 ++目录是正确的。
我终于弄明白了为什么会这样!
在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添加到链接器选项。
使用预编译的SDL2main.lib和SDL.lib为您的VS2015项目库:__iob_func
我设法解决了这个问题。
错误的来源是这行代码,可以在SDLmain源代码中找到。
https://buildbot.libsdl.org/sdl-builds/sdl-visualstudio/sdl-visualstudio-2225.zip
所以我所做的就是编辑该行的SDLmain中的源代码:
fprintf(stderr, "%s: %s\n", title, message);
然后我构建了SDLmain并使用新构建和编辑的方法复制并替换了SDL2库目录中的旧SDLmain.lib。
然后,当我使用SDL2运行程序时,没有出现任何错误消息,并且代码运行顺利。
我不知道这是否会在以后咬我,但所以一切都很顺利。
当你链接到msvcrt.dll而不是msvcr10.dll(或类似的)时会发生这种情况,这是一个很好的计划。因为它可以让您自由地将Visual Studio的运行时库重新分发到最终的软件包中。
该解决方法帮助我(在Visual Studio 2008):
fprintf("%s: %s\n", title, message);
Visual Studio 6及其编译器不需要此代码段。因此#ifdef。
为了给这个已经很丰富的线程带来更多的困惑,我碰巧遇到了fprintf上同样未解决的外部问题
#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 2005(Visual Studio 8.0)下,错误发生在我自己的代码中(我正在编译),而不是第三方。
碰巧这个错误是由我的编译器标志中的/ MD选项触发的。切换到/ MT删除了该问题。这很奇怪,因为通常情况下,静态链接(MT)引发的问题多于动态(MD)...但是为了防止其他问题,我把它放在那里。
在我的情况下,此错误来自我的试验,以删除依赖于MSVC版本的运行时库DLL(msvcr10.dll左右)和/或删除静态运行时库,以从我的可执行文件中删除多余的脂肪。
所以我使用/ NODEFAULTLIB链接器开关,我自制的“msvcrt-light.lib”(当你需要的时候google for it)和main.obj : error LNK2019: unresolved external symbol __imp__fprintf referenced in function _GenerateInfoFile
/ mainCRTStartup()
条目。
自Visual Studio 2015以来就是恕我直言,所以我坚持使用较旧的编译器。
但是,定义符号_NO_CRT_STDIO_INLINE会消除所有麻烦,简单的“Hello World”应用程序再次小3 KB,并且不依赖于异常的DLL。在Visual Studio 2017中测试过。
我用以下功能解决了这个问题。我使用Visual Studio 2019。
WinMainCRTStartup()
因为stdin宏定义了函数调用,“* stdin”表达式是不能使用全局数组初始化器。但是本地数组初始化是可能的。对不起,我英文很差。
对于仍然在寻找上述技巧不起作用的答案的人。静态链接是解决此问题的方法。更改您的运行时库设置,如下所示
FILE* __cdecl __iob_func(void)
{
FILE _iob[] = { *stdin, *stdout, *stderr };
return _iob;
}
对IMO的米兰巴布什科夫来说,这正是替换功能的样子:-)
FILE _iob[] = {*stdin, *stdout, *stderr};
extern "C" FILE * __cdecl __iob_func(void)
{
return _iob;
}
微软对此有一个特别说明(https://msdn.microsoft.com/en-us/library/bb531344.aspx#BK_CRT):
printf和scanf系列函数现在以内联方式定义。
所有printf和scanf函数的定义都已内联移入stdio.h,conio.h和其他CRT头文件中。这是一个重大更改,导致链接器错误(LNK2019,未解析的外部符号),用于在本地声明这些函数但不包含相应CRT头的任何程序。如果可能,您应该更新代码以包含CRT标头(即添加#include)和内联函数,但如果您不想修改代码以包含这些头文件,则另一种解决方案是添加额外的库到您的链接器输入,legacy_stdio_definitions.lib。
要将此库添加到IDE中的链接器输入,请打开项目节点的上下文菜单,选择“属性”,然后在“项目属性”对话框中选择“链接器”,并编辑链接器输入以将legacy_stdio_definitions.lib添加到分号 - 分隔列表。
如果您的项目链接到使用早于2015年的Visual C ++版本编译的静态库,则链接器可能会报告未解析的外部符号。这些错误可能以__imp_ *的形式引用某些stdio函数的_iob,_iob_func或相关导入的内部stdio定义。 Microsoft建议您在升级项目时使用最新版本的Visual C ++编译器和库重新编译所有静态库。如果库是没有源的第三方库,您应该从第三方请求更新的二进制文件,或者将您对该库的使用封装到使用旧版Visual C ++编译器编译的单独DLL中和图书馆。
我在VS2015中遇到了同样的问题。我通过编译VS2015中的SDL2源解决了这个问题。
如上所述,正确的答案是使用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在原始实现方面不能违反两条关键规则:
如果调用该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的不同底层结构。如果结构只包含指针,这可能会有效,但这是一个很大的风险。无论如何,第一个问题使这无关紧要。
我能够想到的唯一一个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;
}
我不知道为什么但是:
#ifdef main
#undef main
#endif
在包括之后,但在你的主要应该从我的经验中解决它。
这个问题的最新解决方案:使用更新的sdl库
“Qazxswpoi”
他们似乎已经解决了这个问题,虽然它只是32位库(我认为)。
链接意味着不能正常工作。深入研究VS2012和VS2015的stdio.h,以下内容对我有用。唉,你必须决定它是否适用于{stdin,stdout,stderr}之一,永远不会超过一个。
https://buildbot.libsdl.org/sdl-builds/sdl-visualstudio/?C=M;O=D
我的建议是不要(尝试)实现__iob_func。
在修复这些错误时:
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);
}
我尝试了其他答案的解决方案,但最终,返回一个libpngd.v110.lib(pngrutil.obj) : error LNK2001: unresolved external symbol ___iob_func
curllib.v110.lib(mprintf.obj) : error LNK2001: unresolved external symbol ___iob_func
C阵列与Windows的内部IOB结构数组不匹配。 @Volker是对的,它永远不会用于FILE*
,stdin
或stdout
。
如果一个库实际使用其中一个流,它将崩溃。只要您的程序不会导致lib使用它们,您就永远不会知道。例如,当CRC在PNG的元数据中不匹配时,stderr
写入png_default_error
。 (通常不是一个值得崩溃的问题)
结论:如果使用stdin,stdout和/或stderr,则无法混合VS2012(Platform Toolset v110 / v110_xp)和VS2015 +库。
解决方案:使用当前版本的VS和匹配的平台工具集重新编译具有stderr
未解析符号的库。