Как было сказано выше, правильный ответ - скомпилировать все с помощью 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? Есть два ключевых правила, которым Microsoft не может противоречить с точки зрения их первоначальной реализации через __iob_func:
- Должен быть массив из трех структур FILE, которые можно проиндексировать так же, как и раньше.
- Структурная схема FILE не может быть изменена.
Любое изменение в любом из вышеперечисленных будет означать, что существующий скомпилированный код, связанный с этим, будет плохо работать, если этот API будет вызван.
Давайте посмотрим, как ФАЙЛ был / определяется.
Сначала определение ФАЙЛА VS2008:
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, и поэтому он будет представлен как блок структур, содержащих пустоту *. При 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;
}