невирішений зовнішній символ __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 -> Додаткові залежності, і я переконався, що каталоги VC ++ є правильними.


1
Чи можете ви довести це, показавши налаштування вашого лінкера.
πάντα ῥεῖ

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

Відповіді:


123

Я нарешті з’ясував, чому це відбувається!

У візуальній студії 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-файлу, складеного з попередніми версіями візуальної студії.

Щоб вирішити проблему, ви можете спробувати визначити, __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. (Ви не можете просто визначити функцію як масив і очікувати, що дзвінки працюватимуть.)
Hans Olsson,

59

Мілану Бабушкові, IMO, саме так повинна виглядати функція заміни :-)

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

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

5
Щойно не вистачає #ifdef для MSVC та для версії MSVC <2015
пауль

1
Як зазначає MarkH в іншій відповіді, яка виглядає правильно, але не спрацює.
Ганс Ольссон

4
@paulm Я думаю, ти маєш на увазі #if defined(_MSC_VER) && (_MSC_VER >= 1900).
Jesse Chisholm

@JesseChisholm, можливо, залежить, чи це стосується також будь-якої відомої майбутньої версії MSVC чи ні;)
паульм

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, відкрийте контекстне меню для вузла проекту, виберіть "Властивості", потім у діалоговому вікні "Властивості проекту" виберіть "Linker" та відредагуйте вхід Linker, щоб додати legacy_stdio_definitions.lib до напівколонки -розділений список.

Якщо ваш проект посилається на статичні бібліотеки, які були складені з випуском Visual C ++ раніше 2015 року, лінкер може повідомити про невирішений зовнішній символ. Ці помилки можуть посилатися на внутрішні визначення stdio для _iob , _iob_func або пов'язаний імпорт для певних функцій stdio у вигляді __imp_ *. Корпорація Майкрософт рекомендує при оновленні проекту перекомпілювати всі статичні бібліотеки з останньою версією компілятора 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 викликається.

Давайте подивимось, як визначався / визначається 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 FILE:

typedef struct _iobuf
{
    void* _Placeholder;
} FILE;

Отже, в цьому суть: структура змінила форму. Існуючий складений код, що посилається на __iob_func, покладається на те, що повернені дані є і масивом, який можна індексувати, і тим, що в цьому масиві елементи знаходяться на однаковій відстані.

Можливі рішення, згадані у відповідях вище у цьому напрямку, не спрацювали (якщо їх викликати) з кількох причин:

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

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

Масив _iob масиву FILE був би складений з 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 ?
Мохаммед БОУЗІДІ

Перш ніж відповісти на моє попереднє запитання, чи нормально, що ці помилки виникають при використанні msbuild 14 та IntelCompiler 2016 та VC100 для компіляції проектів C ++?
Мохаммед БОУЗІДІ

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. Складіть головний проект SDL2.
  5. Використовуйте нові згенеровані вихідні файли SDL2main.lib, SDL2.lib та SDL2.dll у своєму проекті SDL 2 у VS2015.

4
BTW, для побудови SDL 2.0.3 потрібно встановити SDK DirectX у червні 2010 року.
Джо

1
Працювали для мене, дякую !! Але мені потрібно було лише скласти SDL2mainі скопіюватиSDL2main.lib
kgwong

10

Я не знаю чому, але:

#ifdef main
#undef main
#endif

Після включає, але перш ніж ваша основна повинна виправити це з мого досвіду.


1
.... Добре .... тому перед тим, як спробувати це, я чутно сказав собі, що я якось сумніваюся, що це спрацює, але все-таки це було ..... чи можете ви пояснити, чому це працює ...?
Тревор Харт

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

2
Це жахливо погана практика злому, але це 3 лінії, і це працює, і це врятувало мене від необхідності зайчиком в будівництві SDL так ... прекрасно зроблено.
Cheezmeister

1
@Cheezmeister Погана практика і хакі часто потрібні для всього. Особливо те, що їм не потрібно.
XGood


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*масиву С не збігається з масивом внутрішніх структур 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

У моєму випадку ця помилка виникає з мого випробування для видалення залежностей від бібліотеки 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.


-2

Вставте цей код у будь-який із вихідних файлів та відновіть його. Працювали для мене!

#include stdio.h

ФАЙЛ _iob [3];

ФАЙЛ * __cdecl __iob_func (недійсний) {

_iob [0] = * stdin;

_iob [0] = * stdout;

_iob [0] = * stderr;

повернути _iob;

}


вам слід додати форматування з `` `, а також деякі пояснення
Антонін ГАВРЕЛ

Хоча цей код може вирішити питання, включаючи пояснення того, як і чому це вирішує проблему, справді допоможе покращити якість вашої публікації та, ймовірно, призведе до збільшення кількості голосів. Пам'ятайте, що ви відповідаєте на запитання читачів у майбутньому, а не лише про людину, яка зараз задає питання. Будь ласка, відредагуйте свою відповідь, щоб додати пояснення та вказати, які обмеження та припущення застосовуються.
Брайан
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.