Как ответил выше, правильный ответ должен собрать все с 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 - это базовая непрозрачная структура данных, хранящая важные преимущества состояния). Пользователями этой функции являются три макроса 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 не может измениться.
Любое изменение в любом из указанных выше означает, что существующий скомпилированный код, связанный с этим, будет плохо ошибочным, если этот 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;
}
В файле _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;
}
Не могли бы вы проигнорировать это, указав параметры компоновщика, пожалуйста. –
@ πάνταῥεῖ, я связался с SDL2.lib и SDL2main.lib в настройках компоновщика ввода, и я убедился, что каталоги указывают на правильный каталог. – RockFrenzy
Возможный дубликат [ошибка LNK2001 \ _ \ _ imp \ _fprintf Visual Studio 2015 RC] (http://stackoverflow.com/questions/30366552/error-lnk2001-imp-fprintf-visual-studio-2015-rc) – dewaffled