如上回答,正确的答案是用VS2015编译一切,但出于兴趣,以下是我对问题的分析。
这个符号似乎没有在微软提供的任何静态库中定义为 VS2015 的一部分,这是相当奇特的,因为所有其他的都是。要找出原因,我们需要查看该函数的声明,更重要的是查看它的使用方式。
这是来自 Visual Studio 2008 标头的 sn-p:
_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 的原始实现而言,Microsoft 不能违反两个关键规则:
- 必须有一个由三个 FILE 结构组成的数组,它们可以像以前一样被索引。
- 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;
}
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 不同的底层结构。如果结构仅包含指针,这可能会起作用,但这是一个很大的风险。无论如何,第一个问题使这无关紧要。
我能想到的唯一技巧是 __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;
}