如上所述,正确的答案是使用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中的原始实现:
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文件定义:
typedef struct _iobuf
{
void* _Placeholder;
} FILE;
所以问题的症结在于:结构已经改变了形状。现有的引用__iob_func的编译代码依赖于返回的数据既是可以索引的数组,而且在这个数组中元素之间的距离是相同的。
如上面答案中提到的可能的解决方案沿着这些线路将无法工作(如果被调用),原因有几个。
FILE _iob[] = {*stdin, *stdout, *stderr};
extern "C" FILE * __cdecl __iob_func(void)
{
return _iob;
}
_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>
#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
#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;
}