C++ 未解析的外部符号 __imp__fprintf 和 __imp____iob_func,SDL2

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/30412951/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 13:46:31  来源:igfitidea点击:

unresolved external symbol __imp__fprintf and __imp____iob_func, SDL2

c++sdl-2visual-studio-2015unresolved-external

提问by RockFrenzy

Could someone explain what the

有人可以解释一下

__imp__fprintf

__imp__fprintf

and

__imp____iob_func

__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.

我正在尝试使用 SDL2。

I'm using Visual Studio 2015 as compiler.

我使用 Visual Studio 2015 作为编译器。

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.

我已在链接器 -> 输入 -> 附加依赖项中链接到 SDL2.lib 和 SDL2main.lib,并确保 VC++ 目录是正确的。

回答by god

I have finally figured out why this is happening !

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

In visual studio 2015, stdin, stderr, stdout are defined as follow :

在 Visual Studio 2015 中,stdin、stderr、stdout 定义如下:

#define stdin  (__acrt_iob_func(0))
#define stdout (__acrt_iob_func(1))
#define stderr (__acrt_iob_func(2))

But previously, they were defined as:

但以前,它们被定义为:

#define stdin  (&__iob_func()[0])
#define stdout (&__iob_func()[1])
#define stderr (&__iob_func()[2])

So now __iob_func is not defined anymore which leads to a link error when using a .lib file compiled with previous versions of visual studio.

所以现在 __iob_func 不再定义,这会导致在使用使用 Visual Studio 以前版本编译的 .lib 文件时出现链接错误。

To solve the issue, you can try defining __iob_func()yourself which should return an array containing {*stdin,*stdout,*stderr}.

为了解决这个问题,你可以尝试定义__iob_func()自己应该返回一个包含{*stdin,*stdout,*stderr}.

Regarding the other link errors about stdio functions (in my case it was sprintf()), you can add legacy_stdio_definitions.libto your linker options.

关于 stdio 函数的其他链接错误(在我的情况下是sprintf()),您可以将legacy_stdio_definitions.lib添加到您的链接器选项。

回答by smartsl

To Milan Babu?kov, IMO, this is exactly what the replacement function should look like :-)

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

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

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

回答by kingsb

Microsoft has a special note on this (https://msdn.microsoft.com/en-us/library/bb531344.aspx#BK_CRT):

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

The printf and scanf family of functions are now defined inline.

The definitions of all of the printf and scanf functions have been moved inline into stdio.h, conio.h, and other CRT headers. This is a breaking change that leads to a linker error (LNK2019, unresolved external symbol) for any programs that declared these functions locally without including the appropriate CRT headers. If possible, you should update the code to include the CRT headers (that is, add #include ) and the inline functions, but if you do not want to modify your code to include these header files, an alternative solution is to add an additional library to your linker input, legacy_stdio_definitions.lib.

To add this library to your linker input in the IDE, open the context menu for the project node, choose Properties, then in the Project Properties dialog box, choose Linker, and edit the Linker Input to add legacy_stdio_definitions.lib to the semi-colon-separated list.

If your project links with static libraries that were compiled with a release of Visual C++ earlier than 2015, the linker might report an unresolved external symbol. These errors might reference internal stdio definitions for _iob, _iob_func, or related imports for certain stdio functions in the form of __imp_*. Microsoft recommends that you recompile all static libraries with the latest version of the Visual C++ compiler and libraries when you upgrade a project. If the library is a third-party library for which source is not available, you should either request an updated binary from the third party or encapsulate your usage of that library into a separate DLL that you compile with the older version of the Visual C++ compiler and libraries.

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 定义,或__imp_*形式的某些 stdio 函数的相关导入. Microsoft 建议您在升级项目时使用最新版本的 Visual C++ 编译器和库重新编译所有静态库。如果库是第三方库,其源代码不可用,您应该向第三方请求更新的二进制文件,或者将该库的使用封装到一个单独的 DLL 中,然后使用旧版本的 Visual C++ 编译器进行编译和图书馆。

回答by MarkH

As answered above, the right answer is to compile everything with VS2015, but for interest the following is my analysis of the problem.

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

This symbol does not appear to be defined in any static library provided by Microsoft as part of VS2015, which is rather peculiar since all others are. To discover why, we need to look at the declaration of that function and, more importantly, how it's used.

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

Here's a snippet from the Visual Studio 2008 headers:

这是 Visual Studio 2008 标头中的一个片段:

_CRTIMP FILE * __cdecl __iob_func(void);
#define stdin (&__iob_func()[0])
#define stdout (&__iob_func()[1])
#define stderr (&__iob_func()[2])

So we can see that the job of the function is to return the start of an array of FILE objects (not handles, the "FILE *" is the handle, FILE is the underlying opaque data structure storing the important state goodies). The users of this function are the three macros stdin, stdout and stderr which are used for various fscanf, fprintf style calls.

所以我们可以看到函数的工作是返回一个 FILE 对象数组的开始(不是句柄,“FILE *”是句柄,FILE 是存储重要状态的底层不透明数据结构)。此函数的用户是三个宏 stdin、stdout 和 stderr,它们用于各种 fscanf、fprintf 样式调用。

Now let's take a look at how Visual Studio 2015 defines the same things:

现在让我们来看看 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.

因此,替换函数的方法已更改为现在返回文件句柄而不是文件对象数组的地址,并且宏已更改为简单地调用传递标识号的函数。

So why can't they/we provide a compatible API? There are two key rules which Microsoft can't contravene in terms of their original implementation via __iob_func:

那么为什么他们/我们不能提供兼容的 API?就其通过 __iob_func 的原始实现而言,Microsoft 不能违反两条关键规则:

  1. There must be an array of three FILE structures which can be indexed in the same manner as before.
  2. The structural layout of FILE cannot change.
  1. 必须有一个包含三个 FILE 结构的数组,它们可以以与以前相同的方式进行索引。
  2. FILE 的结构布局不能改变。

Any change in either of the above would mean existing compiled code linked against that would go badly wrong if that API is called.

上述任何一项的任何更改都意味着,如果调用该 API,与之链接的现有编译代码将出现严重错误。

Let's take a look at how FILE was/is defined.

让我们来看看 FILE 是如何定义的。

First the VS2008 FILE definition:

首先VS2008 FILE定义:

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:

现在是 VS2015 FILE 定义:

typedef struct _iobuf
{
    void* _Placeholder;
} FILE;

So there is the crux of it: the structure has changed shape. Existing compiled code referring to __iob_func relies upon the fact that the data returned is both an array that can be indexed and that in that array the elements are the same distance apart.

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

The possible solutions mentioned in the answers above along these lines would not work (if called) for a few reasons:

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

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.

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 的反汇编代码证明了这一点。

A secondary issue with the above solution is that it copiesthe content of the FILE structure (*stdin), not the FILE * handle. So any VS2008 code would be looking at a different underlying structure to VS2015. This might work if the structure only contained pointers, but that's a big risk. In any case the first issue renders this irrelevant.

上述解决方案的第二个问题是它复制了 FILE 结构 (*stdin) 的内容,而不是 FILE * 句柄。因此,任何 VS2008 代码都会查看与 VS2015 不同的底层结构。如果结构只包含指针,这可能会起作用,但这是一个很大的风险。无论如何,第一个问题使这无关紧要。

The only hack I've been able to dream up is one in which __iob_func walks the call stack to work out which actual file handle they are looking for (based on the offset added to the returned address) and returns a computed value such that it gives the right answer. This is every bit as insane as it sounds, but the prototype for x86 only (not x64) is listed below for your amusement. It worked okay in my experiments, but your mileage may vary - not recommended for production use!

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

回答by MarkH

I had the same issue in VS2015. I have solved it by compiling the SDL2 sources in VS2015.

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

  1. Go to http://libsdl.org/download-2.0.phpand download SDL 2 source code.
  2. Open SDL_VS2013.sln in VS2015. You will be asked to convert the projects. Do it.
  3. Compile SDL2 project.
  4. Compile SDL2main project.
  5. Use the new generated output files SDL2main.lib, SDL2.lib and SDL2.dll in your SDL 2 project in VS2015.
  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。

回答by The XGood

I don't know why but:

我不知道为什么,但是:

#ifdef main
#undef main
#endif

After the includes but before your main should fix it from my experience.

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

回答by Vhaerun Of Decrepitude

A more recent solution to this problem: Use the more recent sdl libs on

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

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

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

They seem to have fixed the problem, although it's only the 32 bit library (I think).

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

回答by Luc Bloom

My advice is to not (try to) implement __iob_func.

我的建议是不要(尝试)实现 __iob_func。

While fixing these errors:

在修复这些错误时:

libpngd.v110.lib(pngrutil.obj) : error LNK2001: unresolved external symbol ___iob_func curllib.v110.lib(mprintf.obj) : error LNK2001: unresolved external symbol ___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

I tried the other answers' solutions, but in the end, returning a FILE*C-array doesn't match up with an array of Windows' internal IOB structs. @Volker is right that it'll never work for more than one of stdin, stdoutor stderr.

我尝试了其他答案的解决方案,但最终,返回的FILE*C 数组与 Windows 的内部 IOB 结构数组不匹配。@Volker 是对的,它永远不会对超过stdin,stdout或之一起作用stderr

If a library actually USESone of those streams, it will crash. As long as your program doesn't cause the lib to use them, you'll never know. For example, png_default_errorwrites to stderrwhen the CRC doesn't match in the PNG's metadata. (Normally not a crash-worthy issue)

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

Conclusion: It's not possible to mix VS2012 (Platform Toolset v110/v110_xp) and VS2015+ libraries, if they use stdin, stdout and/or stderr.

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

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

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

回答by Volker

To link means not to work properly. Digging into stdio.h of VS2012 and VS2015 the following worked for me. Alas, you have to decide if it should work for one of { stdin, stdout, stderr }, never more than one.

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

回答by soen.kr

I resolve this problem with following function. I use Visual Studio 2019.

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

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

because stdin Macro defined function call, "*stdin" expression is cannot used global array initializer. But local array initialier is possible. sorry, I am poor at english.

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