неразрешенный внешний символ __imp__fprintf и __imp____iob_func, SDL2


108

Может ли кто-нибудь объяснить, что

__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 ++ верны.


1
Не могли бы вы доказать это, показав, пожалуйста, свои настройки компоновщика.
πάντα ῥεῖ

@ πάνταῥεῖ, я связался с SDL2.lib и SDL2main.lib в настройках входного компоновщика и убедился, что каталоги указывают на правильный каталог.
RockFrenzy

Ответы:


123

Я наконец понял, почему это происходит!

В 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 в параметры компоновщика.


1
Спасибо, что отследили это. IIRC проблема с {* stdin, * stdout, * stderr} может заключаться в том, что разные единицы компиляции могут иметь свою «собственную» копию stdin, поэтому эти функции вызывались напрямую.
Стивен Р. Лумис

3
это тоже решило для меня, просто напоминание для использования extern "C"в объявлении / определении.
Варгас

4
Может кто-нибудь написать, как именно должна выглядеть функция замены? Я пробовал разные варианты и все время получаю ошибки компиляции. Спасибо.
Милан Бабушков

55
extern "C" { FILE __iob_func[3] = { *stdin,*stdout,*stderr }; }
PoL0

1
Приведенное выше определение iob_func не работает, правильное определение см. В ответе MarkH. (Вы не можете просто определить функцию как массив и ожидать, что вызовы будут работать.)
Ханс Олссон

59

Милану Бабушкову, ИМО, функция замены должна выглядеть именно так :-)

FILE _iob[] = {*stdin, *stdout, *stderr};

extern "C" FILE * __cdecl __iob_func(void)
{
    return _iob;
}

5
Просто отсутствует #ifdef для MSVC и для версии MSVC <2015
paulm

1
Как отмечает MarkH в другом ответе, он выглядит правильным, но не работает.
Hans Olsson

4
@paulm Думаю, ты имеешь в виду #if defined(_MSC_VER) && (_MSC_VER >= 1900).
Джесси

@JesseChisholm, возможно, зависит от того, относится ли это также ко всем известным будущим версиям MSVC или нет;)
Paulm

42

У Microsoft есть особое примечание по этому поводу ( https://msdn.microsoft.com/en-us/library/bb531344.aspx#BK_CRT ):

Семейства функций 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 ++. и библиотеки.


7
Или #pragma comment(lib, "legacy_stdio_definitions.lib")- но это не исправляет __imp___iob_func- есть ли унаследованная библиотека для этого?
bytecode77

29

Как было сказано выше, правильный ответ - скомпилировать все с помощью 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:

  1. Должен быть массив из трех структур FILE, которые можно проиндексировать так же, как и раньше.
  2. Структурная схема 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;
}

Правильный ответ: самое простое исправление - обновить проект до VS2015 и затем скомпилировать.
Акумабурн

2
В моем случае мне нужно обновить многие проекты (проекты C ++ и C #) из Visual Studio 2013, чтобы использовать Visual Studio 2015 Update 3. Я хочу сохранить VC100 (компилятор Visual Studio 2010 C ++) при создании проектов C ++, но у меня такие же ошибки как указано выше. Я исправил imp _fprintf , добавив legacy_stdio_definitions.lib в компоновщик. Как я могу также исправить _imp____iob_func ?
Mohamed BOUZIDI

Прежде чем ответить на мой предыдущий вопрос, нормально ли, что эти ошибки возникают при использовании msbuild 14 и IntelCompiler 2016 и VC100 для компиляции проектов C ++?
Mohamed BOUZIDI

1
что я могу сделать для компиляции x64?
athos

28

У меня была такая же проблема в VS2015. Я решил это, скомпилировав исходники SDL2 в VS2015.

  1. Перейдите на http://libsdl.org/download-2.0.php и загрузите исходный код SDL 2.
  2. Откройте SDL_VS2013.sln в VS2015 . Вам будет предложено преобразовать проекты. Сделай это.
  3. Скомпилируйте проект SDL2.
  4. Скомпилируйте проект SDL2main.
  5. Используйте новые сгенерированные выходные файлы SDL2main.lib, SDL2.lib и SDL2.dll в своем проекте SDL 2 в VS2015.

4
Кстати, для сборки SDL 2.0.3 требуется установить DirectX SDK за июнь 2010 года.
Джо

1
Сработало у меня, спасибо !! Но мне нужно было только скомпилировать SDL2mainи скопироватьSDL2main.lib
kgwong

10

Не знаю почему, но:

#ifdef main
#undef main
#endif

По моему опыту, после включения, но до того, как ваш основной должен исправить это.


1
.... Хорошо .... Итак, прежде чем попробовать это, я вслух сказал себе, что я почему-то сомневаюсь, что это сработает, но это полностью помогло ... Вы можете объяснить, почему это работает ...?
Trevor Hart

1
@TrevorHart Я считаю, что он не определяет "неисправный" основной SDL- файл, содержащий ссылки на "неопределенные" буферы, и если ваш использует ваши буферы, то он работает хорошо и хорошо.
The XGood

2
Это ужасный плохой практический прием, но это 3 строчки, и он работает, и это спасло меня от того, что мне приходилось ломать голову над созданием SDL, так что ... красиво сделано.
Cheezmeister

1
@Cheezmeister Плохая практика и хаки часто требуются для всего. Особенно те вещи, которым они не нужны.
The XGood 08


7

Ссылка означает некорректную работу. Копаясь в 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);
}

7

Мой совет - не (пытайтесь) реализовывать __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.

Если библиотека действительно ИСПОЛЬЗУЕТ один из этих потоков, произойдет сбой . Пока ваша программа не заставляет библиотеку их использовать, вы никогда не узнаете . Например, png_default_errorзаписывает, stderrкогда CRC не соответствует метаданным PNG. (Обычно это не проблема, вызывающая сбой)

Вывод: невозможно смешивать библиотеки VS2012 (Platform Toolset v110 / v110_xp) и VS2015 +, если они используют stdin, stdout и / или stderr.

Решение: перекомпилируйте библиотеки, в которых есть __iob_funcнеразрешенные символы, с вашей текущей версией VS и соответствующим набором инструментов платформы.



2

Я решаю эту проблему с помощью следующей функции. Я использую Visual Studio 2019.

FILE* __cdecl __iob_func(void)
{
    FILE _iob[] = { *stdin, *stdout, *stderr };
    return _iob;
}

поскольку stdin Макрос определяет вызов функции, выражение "* stdin" не может использоваться инициализатором глобального массива. Но возможен инициатор локального массива. извините, я плохо говорю по английски.


1

Для тех, кто все еще ищет ответ, где вышеуказанные уловки не сработали. Статические ссылки - способ решить эту проблему. Измените настройки библиотеки времени выполнения, как показано ниже

Project properties --> C/C++ --> Code generation --> Runtime Library --> Multi-threaded Debug (/MTd) instead of /MDd


Вот обсуждение этого решения: social.msdn.microsoft.com/Forums/vstudio/en-US/…
Sisir

0

Мне удалось исправить проблему.

Источником ошибки была эта строка кода, которую можно найти в исходном коде SDLmain.

fprintf(stderr, "%s: %s\n", title, message);

Итак, что я сделал, так это отредактировал исходный код в SDLmain этой строки:

fprintf("%s: %s\n", title, message);

А затем я собрал SDLmain, скопировал и заменил старый SDLmain.lib в каталоге моей библиотеки SDL2 новым созданным и отредактированным.

Затем, когда я запустил свою программу с SDL2, сообщений об ошибках не было, и код прошел гладко.

Не знаю, укусит ли это меня потом, но так что все идет отлично.


Ваше изменение само по себе является ошибкой и не устранило бы проблему, описанную в вашем вопросе. Это просто совпадение, что вы больше не получаете ошибок компоновщика, что, вероятно, является исключительно результатом того, как вы перестроили библиотеку.
Росс Ридж

@RossRidge, о да, это могло быть именно так. Ах хорошо.
RockFrenzy

0

Это может произойти, если вы создадите ссылку на 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.


0

Чтобы внести больше путаницы в этот и без того богатый поток, я случайно наткнулся на тот же неразрешенный внешний вид на 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) ... но на всякий случай я помещаю его туда.


0

В моем случае эта ошибка возникла из-за моего пробного удаления зависимостей от MSVC-версии зависимой библиотеки времени выполнения DLL (msvcr10.dll или около того) и / или удаления статической библиотеки времени выполнения, чтобы удалить лишний жир из моих исполняемых файлов.

Поэтому я использую переключатель компоновщика / NODEFAULTLIB, мой самодельный файл "msvcrt-light.lib" (при необходимости используйте Google) и mainCRTStartup()/WinMainCRTStartup() entry.

Это IMHO с Visual Studio 2015, поэтому я придерживался старых компиляторов.

Однако определение символа _NO_CRT_STDIO_INLINE устраняет все проблемы , и простое приложение «Hello World» снова имеет размер 3 КБ и не зависит от необычных DLL. Протестировано в Visual Studio 2017.


-2

Вставьте этот код в любой из ваших исходных файлов и заново соберите. Сработало у меня!

#include stdio.h

ФАЙЛ _iob [3];

ФАЙЛ * __cdecl __iob_func (void) {

_iob [0] = * стандартный ввод;

_iob [0] = * стандартный вывод;

_iob [0] = * stderr;

return _iob;

}


вы должны добавить форматирование с помощью '', а также некоторые пояснения
Антонин ГАВРЕЛЬ

Хотя этот код может решить вопрос, в том числе объяснение того, как и почему это решает проблему, действительно поможет улучшить качество вашего сообщения и, вероятно, приведет к большему количеству голосов за. Помните, что вы отвечаете на вопрос для будущих читателей, а не только для человека, который задает его сейчас. Пожалуйста , измените свой ответ , чтобы добавить объяснения и дать указание о том , что применять ограничения и допущения.
Брайан
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.