Неразрешенный внешний символ __imp__fprintf и __imp____iob_func, SDL2
Может ли кто-нибудь объяснить, что
__imp__fprintf
а также
__imp____iob_func
нерешенные внешние средства?
Потому что я получаю эти ошибки, когда пытаюсь скомпилировать:
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
Я уже могу сказать, что проблема не в неправильной связи. Я все правильно связал, но по какой-то причине он не скомпилируется.
Я пытаюсь использовать SDL2.
Я использую Visual Studio 2015 в качестве компилятора.
Я связал с SDL2.lib и SDL2main.lib в Linker -> Input -> Additional Dependencies, и я убедился, что каталоги VC++ верны.
20 ответов
Я наконец понял, почему это происходит!
В Visual Studio 2015 stdin, stderr, stdout определяются следующим образом:
#define stdin (__acrt_iob_func(0))
#define stdout (__acrt_iob_func(1))
#define stderr (__acrt_iob_func(2))
Но ранее они были определены как:
#define stdin (&__iob_func()[0])
#define stdout (&__iob_func()[1])
#define stderr (&__iob_func()[2])
Так что теперь __iob_func больше не определяется, что приводит к ошибке ссылки при использовании файла.lib, скомпилированного с предыдущими версиями Visual Studio.
Чтобы решить эту проблему, вы можете попробовать определить __iob_func()
себя, который должен вернуть массив, содержащий {*stdin,*stdout,*stderr}
,
Относительно других ошибок ссылки на функции stdio (в моем случае это было sprintf()
), вы можете добавить legacy_stdio_definitions.lib в параметры компоновщика.
Милану Бабушкову, ИМО, именно так должна выглядеть функция замены:-)
FILE _iob[] = {*stdin, *stdout, *stderr};
extern "C" FILE * __cdecl __iob_func(void)
{
return _iob;
}
У Microsoft есть специальное примечание по этому вопросу ( https://msdn.microsoft.com/en-us/library/bb531344.aspx):
Семейство функций printf и scanf теперь определены как встроенные.
Определения всех функций printf и scanf были встроены в stdio.h, conio.h и другие заголовки CRT. Это серьезное изменение, которое приводит к ошибке компоновщика (LNK2019, неразрешенный внешний символ) для любых программ, которые объявили эти функции локально, без включения соответствующих заголовков CRT. Если возможно, вы должны обновить код, включив в него заголовки CRT (то есть добавить #include) и встроенные функции, но если вы не хотите изменять свой код для включения этих заголовочных файлов, альтернативным решением является добавление дополнительного библиотека для ввода вашего компоновщика, legacy_stdio_definitions.lib.
Чтобы добавить эту библиотеку к входу компоновщика в IDE, откройте контекстное меню для узла проекта, выберите "Свойства", затем в диалоговом окне "Свойства проекта" выберите "Компоновщик" и отредактируйте вход компоновщика, добавив legacy_stdio_definitions.lib в точку с запятой. разделенный список.
Если ваш проект связан со статическими библиотеками, которые были скомпилированы с выпуском Visual C++ ранее 2015 года, компоновщик может сообщить о неразрешенном внешнем символе. Эти ошибки могут ссылаться на внутренние определения stdio для _iob, _iob_func или связанные импорты для определенных функций stdio в форме __imp_ *. Microsoft рекомендует перекомпилировать все статические библиотеки с последней версией компилятора Visual C++ и библиотек при обновлении проекта. Если библиотека является сторонней библиотекой, источник которой недоступен, вам следует либо запросить обновленный двоичный файл у третьей стороны, либо инкапсулировать использование этой библиотеки в отдельную DLL, которую вы компилируете с более старой версией компилятора Visual C++. и библиотеки.
Как уже было сказано выше, правильный ответ - собрать все с 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.
Давайте посмотрим, как был определен / определен FILE.
Сначала определение ФАЙЛА 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, и поэтому он будет размечен как блок структур, содержащих 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;
}
У меня была такая же проблема в VS2015. Я решил это, скомпилировав источники SDL2 в VS2015.
- Перейдите по http://libsdl.org/download-2.0.php и загрузите исходный код SDL 2.
- Откройте SDL_VS2013.sln в VS2015. Вам будет предложено преобразовать проекты. Сделай это.
- Скомпилируйте проект SDL2.
- Скомпилируйте SDL2основный проект.
- Используйте новые сгенерированные выходные файлы SDL2main.lib, SDL2.lib и SDL2.dll в вашем проекте SDL 2 в VS2015.
Я не знаю почему, но:
#ifdef main
#undef main
#endif
После того, как включает, но прежде, чем ваш главный должен исправить это из моего опыта.
Более свежее решение этой проблемы: используйте более поздние библиотеки sdl на
" https://buildbot.libsdl.org/sdl-builds/sdl-visualstudio/?C=M;O=D"
Похоже, они решили проблему, хотя это всего лишь 32-битная библиотека (я думаю).
Мой совет - не пытаться реализовать __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
Я пробовал решения других ответов, но в конце концов, возвращая FILE*
C-массив не совпадает с массивом внутренних структур IOB Windows. @Volker прав, что он никогда не сработает для более чем одного из stdin
, stdout
или же stderr
,
Если библиотека на самом деле использует один из этих потоков, она потерпит крах. Пока ваша программа не заставляет их использовать lib, вы никогда не узнаете. Например, png_default_error
пишет stderr
когда CRC не совпадает в метаданных PNG. (Обычно не является проблемой, достойной сбоя)
Вывод: Невозможно смешивать библиотеки VS2012 (Platform Toolset v110/v110_xp) и VS2015+, если они используют stdin, stdout и / или stderr.
Решение: перекомпилируйте ваши библиотеки, которые имеют __iob_func
неразрешенные символы в текущей версии VS и соответствующий набор инструментов платформы.
Связать означает не работать должным образом. Копаясь в stdio.h VS2012 и VS2015 у меня сработало следующее. Увы, вы должны решить, должен ли он работать для одного из { 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);
}
Я решаю эту проблему с помощью следующей функции. Я использую Visual Studio 2019.
FILE* __cdecl __iob_func(void)
{
FILE _iob[] = { *stdin, *stdout, *stderr };
return _iob;
}
Поскольку вызов функции, определяемой макросом stdin, выражение "*stdin" не может использоваться инициализатором глобального массива. Но инициализатор локального массива возможен. извини, я плохо знаю английский.
Используйте предварительно скомпилированные SDL2main.lib и SDL.lib для библиотеки вашего проекта VS2015: https://buildbot.libsdl.org/sdl-builds/sdl-visualstudio/sdl-visualstudio-2225.zip
Для тех, кто все еще ищет ответ, где вышеуказанные уловки не сработали. Статические ссылки - способ решить эту проблему. Измените настройки библиотеки времени выполнения, как показано ниже
Project properties --> C/C++ --> Code generation --> Runtime Library --> Multi-threaded Debug (/MTd) instead of /MDd
Это работает с Visual-Studio 2017:
#include<corecrt_wstdio.h>
FILE* __cdecl __iob_func(unsigned const id)
{
return(__acrt_iob_func(id));
}
В моем случае эта ошибка возникает из-за моей попытки удалить зависимости от DLL-библиотеки, зависящей от версии MSVC (msvcr10.dll или около того), и / или удалить статическую библиотеку времени выполнения, чтобы удалить лишний жир из моих исполняемых файлов.
Поэтому я использую переключатель компоновщика /NODEFAULTLIB, мой самодельный "msvcrt-light.lib" (Google для него, когда вам нужно), и mainCRTStartup()
/ WinMainCRTStartup()
записей.
Это IMHO начиная с Visual Studio 2015, поэтому я остановился на старых компиляторах.
Однако определение символа _NO_CRT_STDIO_INLINE устраняет все хлопоты, и простое приложение "Hello World" снова имеет размер 3 КБ и не зависит от необычных библиотек DLL. Протестировано в Visual Studio 2017.
Чтобы внести еще большую путаницу в этот и без того богатый поток, я случайно натолкнулся на тот же неразрешенный внешний элемент на fprintf
main.obj : error LNK2019: unresolved external symbol __imp__fprintf referenced in function _GenerateInfoFile
Даже если в моем случае это было в совершенно ином контексте: в Visual Studio 2005 (Visual Studio 8.0) и ошибка происходила в моем собственном коде (то же самое, что я компилировал), а не в стороннем поставщике.
Случилось так, что эта ошибка была вызвана параметром /MD в моих флагах компилятора. Переключение на /MT устранило проблему. Это странно, потому что обычно статическое связывание (MT) создает больше проблем, чем динамическое (MD)... но на всякий случай, если оно служит другим, я помещаю это туда.
У меня возникла такая же проблема (неразрешенный внешний символ __impl_printf) при попытке связать .lib с моим проектом vs2015.
единственное решение, которое сработало для меня, это проект-> свойства-> компоновщик-> Командная строка в поле дополнительных параметров добавить:legacy_stdio_definitions.lib
Может быть, это может вам помочь. Я добавил Shell32.lib в свой Linker -> Input -> Additional Dependencies, и он остановил эту ошибку. Я узнал об этом из этого сообщения: https://discourse.libsdl.org/t/windows-build-fails-with-missing-symbol-imp-commandlinetoargvw/27256/3
Это может произойти, когда вы ссылаетесь на msvcrt.dll вместо msvcr10.dll (или подобного), что является хорошим планом. Потому что это освободит вас от перераспределения библиотеки времени выполнения Visual Studio внутри вашего окончательного пакета программного обеспечения.
Этот обходной путь помогает мне (в Visual Studio 2008):
#if _MSC_VER >= 1400
#undef stdin
#undef stdout
#undef stderr
extern "C" _CRTIMP extern FILE _iob[];
#define stdin _iob
#define stdout (_iob+1)
#define stderr (_iob+2)
#endif
Этот фрагмент не нужен для Visual Studio 6 и его компилятора. Поэтому #ifdef.
Мне удалось решить проблему.
Источником ошибки была эта строка кода, которую можно найти в исходном коде SDLmain.
fprintf(stderr, "%s: %s\n", title, message);
Итак, я также редактировал исходный код в SDLmain этой строки:
fprintf("%s: %s\n", title, message);
А затем я собрал SDLmain, скопировал и заменил старый SDLmain.lib в моем каталоге библиотек SDL2 на вновь созданный и отредактированный.
Затем, когда я запустил мою программу с SDL2, никаких сообщений об ошибках не появилось, и код работал гладко.
Я не знаю, будет ли это кусать меня позже, но так как все идет хорошо.
Вставьте этот код в любой из ваших исходных файлов и заново соберите. Сработало у меня!
#include stdio.h
ФАЙЛ _iob[3];
ФАЙЛ * __cdecl __iob_func(void){
_iob [0] = * стандартный ввод;
_iob [0] = * стандартный вывод;
_iob [0] = *stderr;
return _iob;
}