simbolo esterno non risolto __imp__fprintf e __imp____iob_func, SDL2


108

Qualcuno potrebbe spiegare cos'è il file

__imp__fprintf

e

__imp____iob_func

mezzi esterni irrisolti?

Perché ottengo questi errori quando provo a compilare:

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

Posso già dire che il problema non è il collegamento sbagliato. Ho collegato tutto correttamente, ma per qualche motivo non verrà compilato.

Sto cercando di utilizzare SDL2.

Utilizzo Visual Studio 2015 come compilatore.

Ho collegato a SDL2.lib e SDL2main.lib in Linker -> Input -> Additional Dependencies e mi sono assicurato che le directory VC ++ siano corrette.


1
Potresti provarlo mostrando le impostazioni del linker per favore.
πάντα ῥεῖ

@ πάνταῥεῖ, ho collegato a SDL2.lib e SDL2main.lib nelle impostazioni del linker di input e mi sono assicurato che le directory puntino alla directory giusta.
RockFrenzy

Risposte:


123

Finalmente ho capito perché sta succedendo!

In visual studio 2015, stdin, stderr, stdout sono definiti come segue:

#define stdin  (__acrt_iob_func(0))
#define stdout (__acrt_iob_func(1))
#define stderr (__acrt_iob_func(2))

Ma in precedenza, erano definiti come:

#define stdin  (&__iob_func()[0])
#define stdout (&__iob_func()[1])
#define stderr (&__iob_func()[2])

Quindi ora __iob_func non è più definito, il che porta a un errore di collegamento quando si utilizza un file .lib compilato con versioni precedenti di visual studio.

Per risolvere il problema, puoi provare a definire __iob_func()te stesso che dovrebbe restituire un array contenente {*stdin,*stdout,*stderr}.

Per quanto riguarda gli altri errori di collegamento sulle funzioni stdio (nel mio caso lo era sprintf()), puoi aggiungere legacy_stdio_definitions.lib alle opzioni del linker.


1
Grazie per averlo rintracciato. IIRC il problema con {* stdin, * stdout, * stderr} potrebbe essere che diverse unità di compilazione potrebbero avere la loro "propria" copia di stdin, motivo per cui queste funzioni sono state chiamate direttamente.
Steven R. Loomis

3
che ha risolto anche per me, solo un promemoria da utilizzare extern "C"nella dichiarazione / definizione.
Vargas

4
Qualcuno può scrivere esattamente come dovrebbe apparire la funzione di sostituzione? Ho provato diverse varianti e continuo a ricevere errori di compilazione. Grazie.
Milan Babuškov

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

1
La definizione iob_func sopra non funziona, vedere la risposta di MarkH per una definizione corretta. (Non puoi semplicemente definire una funzione come un array e aspettarti che le chiamate funzionino.)
Hans Olsson

59

Per Milan Babuškov, IMO, questo è esattamente come dovrebbe essere la funzione di sostituzione :-)

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

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

5
Manca solo un #ifdef per MSVC e per la versione MSVC <2015
paulm

1
Come nota MarkH in un'altra risposta che sembra corretta, ma non funzionerà.
Hans Olsson

4
@paulm penso tu intenda #if defined(_MSC_VER) && (_MSC_VER >= 1900).
Jesse Chisholm

@JesseChisholm forse, dipende se questo vale anche per ogni futura versione conosciuta di MSVC o no;)
paulm

42

Microsoft ha una nota speciale su questo ( https://msdn.microsoft.com/en-us/library/bb531344.aspx#BK_CRT ):

Le famiglie di funzioni printf e scanf sono ora definite in linea.

Le definizioni di tutte le funzioni printf e scanf sono state spostate inline in stdio.h , conio.h e altre intestazioni CRT. Questa è una modifica sostanziale che porta a un errore del linker (LNK2019, simbolo esterno non risolto) per tutti i programmi che hanno dichiarato queste funzioni localmente senza includere le intestazioni CRT appropriate. Se possibile, è necessario aggiornare il codice per includere le intestazioni CRT (ovvero, aggiungere #include) e le funzioni inline, ma se non si desidera modificare il codice per includere questi file di intestazione, una soluzione alternativa è aggiungere un ulteriore library al tuo input del linker, legacy_stdio_definitions.lib .

Per aggiungere questa libreria all'input del linker nell'IDE, apri il menu contestuale per il nodo del progetto, scegli Proprietà, quindi nella finestra di dialogo Proprietà del progetto, scegli Linker e modifica l'Input del linker per aggiungere legacy_stdio_definitions.lib al punto e virgola -elenco separato.

Se il tuo progetto si collega a librerie statiche compilate con una versione di Visual C ++ precedente al 2015, il linker potrebbe segnalare un simbolo esterno non risolto. Questi errori potrebbero fare riferimento a definizioni stdio interne per _iob , _iob_func o importazioni correlate per determinate funzioni stdio sotto forma di __imp_ *. Microsoft consiglia di ricompilare tutte le librerie statiche con la versione più recente del compilatore e delle librerie di Visual C ++ quando si aggiorna un progetto. Se la libreria è una libreria di terze parti per la quale l'origine non è disponibile, è necessario richiedere un file binario aggiornato da terze parti o incapsulare l'utilizzo di tale libreria in una DLL separata che si compila con la versione precedente del compilatore Visual C ++ e biblioteche.


7
Oppure #pragma comment(lib, "legacy_stdio_definitions.lib"), ma questo non risolve il problema __imp___iob_func, esiste anche una libreria legacy per questo?
bytecode77

29

Come risposto sopra, la risposta giusta è compilare tutto con VS2015, ma per interesse la seguente è la mia analisi del problema.

Questo simbolo non sembra essere definito in nessuna libreria statica fornita da Microsoft come parte di VS2015, il che è piuttosto peculiare poiché tutte le altre lo sono. Per scoprire perché, dobbiamo esaminare la dichiarazione di quella funzione e, cosa più importante, come viene utilizzata.

Ecco un frammento delle intestazioni di Visual Studio 2008:

_CRTIMP FILE * __cdecl __iob_func(void);
#define stdin (&__iob_func()[0])
#define stdout (&__iob_func()[1])
#define stderr (&__iob_func()[2])

Quindi possiamo vedere che il compito della funzione è di restituire l'inizio di un array di oggetti FILE (non gli handle, "FILE *" è l'handle, FILE è la struttura dati opaca sottostante che memorizza le importanti chicche di stato). Gli utenti di questa funzione sono le tre macro stdin, stdout e stderr che sono usate per varie chiamate in stile fscanf, fprintf.

Ora diamo un'occhiata a come Visual Studio 2015 definisce le stesse cose:

_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))

Quindi l'approccio è cambiato per la funzione di sostituzione per ora restituire l'handle del file piuttosto che l'indirizzo dell'array di oggetti file, e le macro sono cambiate per chiamare semplicemente la funzione passando un numero identificativo.

Allora perché non possono fornire un'API compatibile? Ci sono due regole chiave che Microsoft non può contravvenire in termini di implementazione originale tramite __iob_func:

  1. Deve esserci un array di tre strutture FILE che possono essere indicizzate nello stesso modo di prima.
  2. Il layout strutturale di FILE non può cambiare.

Qualsiasi cambiamento in uno dei precedenti significherebbe che il codice compilato esistente collegato a quello andrebbe gravemente storto se viene chiamata quell'API.

Diamo un'occhiata a come è stato / è definito FILE.

Prima la definizione del FILE VS2008:

struct _iobuf {
        char *_ptr;
        int   _cnt;
        char *_base;
        int   _flag;
        int   _file;
        int   _charbuf;
        int   _bufsiz;
        char *_tmpfname;
        };
typedef struct _iobuf FILE;

E ora la definizione del FILE VS2015:

typedef struct _iobuf
{
    void* _Placeholder;
} FILE;

Quindi c'è il punto cruciale: la struttura ha cambiato forma. Il codice compilato esistente che fa riferimento a __iob_func si basa sul fatto che i dati restituiti sono sia un array che può essere indicizzato sia che in quell'array gli elementi sono alla stessa distanza.

Le possibili soluzioni menzionate nelle risposte sopra in questo senso non funzionerebbero (se chiamate) per alcuni motivi:

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

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

L'array FILE _iob verrebbe compilato con VS2015 e quindi sarebbe disposto come un blocco di strutture contenenti un vuoto *. Supponendo un allineamento a 32 bit, questi elementi sarebbero a 4 byte di distanza. Quindi _iob [0] è all'offset 0, _iob [1] è all'offset 4 e _iob [2] è all'offset 8. Il codice chiamante invece si aspetterà che FILE sia molto più lungo, allineato a 32 byte sul mio sistema, e così prenderà l'indirizzo dell'array restituito e aggiungerà 0 byte per arrivare all'elemento zero (quello va bene), ma per _iob [1] dedurrà che deve aggiungere 32 byte e per _iob [2] dedurrà che deve aggiungere 64 byte (perché è così che appariva nelle intestazioni di VS2008). E in effetti il ​​codice smontato per VS2008 lo dimostra.

Un problema secondario con la soluzione sopra è che copia il contenuto della struttura FILE (* stdin), non l'handle FILE *. Quindi qualsiasi codice VS2008 guarderebbe una struttura sottostante diversa rispetto a VS2015. Questo potrebbe funzionare se la struttura contenesse solo puntatori, ma è un grosso rischio. In ogni caso la prima questione lo rende irrilevante.

L'unico trucco che sono stato in grado di immaginare è quello in cui __iob_func controlla lo stack di chiamate per capire quale handle di file effettivo stanno cercando (in base all'offset aggiunto all'indirizzo restituito) e restituisce un valore calcolato in modo tale che dà la risposta giusta. Questo è altrettanto folle come sembra, ma il prototipo solo per x86 (non x64) è elencato di seguito per il tuo divertimento. Ha funzionato bene nei miei esperimenti, ma il tuo chilometraggio può variare - non consigliato per l'uso in produzione!

#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;
}

La risposta corretta è questa, la soluzione più semplice è quella di aggiornare il progetto a VS2015 e quindi compilare.
Akumaburn

2
Nel mio caso, ho bisogno di aggiornare molti progetti (progetti C ++ e C #) da Visual Studio 2013 per utilizzare Visual Studio 2015 Update 3. Voglio mantenere VC100 (compilatore C ++ Visual studio 2010) durante la creazione di progetti C ++ ma ho gli stessi errori come sopra. Ho corretto imp _fprintf aggiungendo legacy_stdio_definitions.lib al linker. Come posso correggere anche _imp____iob_func ?
Mohamed BOUZIDI

Prima di rispondere alla mia domanda precedente, è normale che si verifichino questi errori quando si utilizzano msbuild 14 e IntelCompiler 2016 e VC100 per compilare progetti C ++?
Mohamed BOUZIDI

1
cosa posso fare per una compilation x64?
athos

28

Ho avuto lo stesso problema in VS2015. L'ho risolto compilando i sorgenti SDL2 in VS2015.

  1. Vai a http://libsdl.org/download-2.0.php e scarica il codice sorgente di SDL 2.
  2. Apri SDL_VS2013.sln in VS2015 . Ti verrà chiesto di convertire i progetti. Fallo.
  3. Compilare il progetto SDL2.
  4. Compilare il progetto SDL2main.
  5. Utilizza i nuovi file di output generati SDL2main.lib, SDL2.lib e SDL2.dll nel tuo progetto SDL 2 in VS2015.

4
A proposito, la creazione di SDL 2.0.3 richiede l'installazione di DirectX SDK di giugno 2010.
Joe

1
Ha funzionato per me, grazie !! Ma avevo solo bisogno di compilare SDL2maine copiareSDL2main.lib
kgwong

10

Non so perché ma:

#ifdef main
#undef main
#endif

Dopo gli include ma prima che il tuo main dovrebbe risolverlo dalla mia esperienza.


1
.... Okay .... quindi prima di provare questo mi sono detto udibilmente che in qualche modo dubito che funzionerà, ma lo ha fatto completamente ..... puoi spiegare perché funziona ...?
Trevor Hart

1
@TrevorHart Credo che undefines un main SDL "difettoso" includeva riferimenti ai buffer "undefined" e se il tuo usa i tuoi buffer, allora funziona bene e bene.
L'XGood

2
Questo è un orribile trucco per cattive pratiche, ma è di 3 righe e funziona e mi ha salvato dal dover rabbithole per costruire SDL così ... ben fatto.
Cheezmeister

1
@Cheezmeister Cattive pratiche e hack sono spesso necessari per tutto. Soprattutto le cose che non dovrebbero averne bisogno.
L'XGood


7

Collegare significa non funzionare correttamente. Scavando in stdio.h di VS2012 e VS2015 il seguente ha funzionato per me. Ahimè, devi decidere se dovrebbe funzionare per uno di {stdin, stdout, stderr}, mai più di uno.

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

Il mio consiglio è di non (provare a) implementare __iob_func.

Durante la correzione di questi errori:

libpngd.v110.lib(pngrutil.obj) : error LNK2001: unresolved external symbol ___iob_func curllib.v110.lib(mprintf.obj) : error LNK2001: unresolved external symbol ___iob_func

Ho provato le soluzioni delle altre risposte, ma alla fine, restituire un FILE*C-array non corrisponde a un array di strutture IOB interne di Windows. @Volker ha ragione che non funzionerà mai per più di uno di stdin, stdouto stderr.

Se una libreria UTILIZZA effettivamente uno di quei flussi, andrà in crash . Finché il tuo programma non fa sì che la libreria li usi, non lo saprai mai . Ad esempio, png_default_errorscrive stderrquando il CRC non corrisponde nei metadati del PNG. (Normalmente non è un problema degno di un crash)

Conclusione: non è possibile combinare le librerie VS2012 (Platform Toolset v110 / v110_xp) e VS2015 +, se utilizzano stdin, stdout e / o stderr.

Soluzione: ricompilare le librerie che hanno __iob_funcsimboli non risolti con la versione corrente di VS e un set di strumenti della piattaforma corrispondente.



2

Risolvo questo problema con la seguente funzione. Uso Visual Studio 2019.

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

poiché la chiamata di funzione definita da macro stdin, l'espressione "* stdin" non può essere utilizzata dall'inizializzatore di array globale. Ma l'inizializzazione dell'array locale è possibile. scusa, sono povero in inglese.


1

Per chiunque stia ancora cercando una risposta in cui i trucchi di cui sopra non hanno funzionato. Il collegamento statico è il modo per risolvere questo problema. Modificare le impostazioni della libreria di runtime come di seguito

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


Ecco una discussione su questa soluzione: social.msdn.microsoft.com/Forums/vstudio/en-US/…
Sisir

0

Sono riuscito a risolvere il problema.

La fonte dell'errore era questa riga di codice, che può essere trovata nel codice sorgente SDLmain.

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

Quindi quello che ho fatto è stato modificare anche il codice sorgente in SDLmain di quella riga:

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

Quindi ho creato SDLmain e ho copiato e sostituito il vecchio SDLmain.lib nella mia directory della libreria SDL2 con quello appena creato e modificato.

Quindi, quando ho eseguito il mio programma con SDL2, non sono stati visualizzati messaggi di errore e il codice ha funzionato senza problemi.

Non so se questo mi morderà più tardi, ma va tutto bene.


La tua modifica è un errore in sé e per sé e non avrebbe risolto il problema descritto nella tua domanda. È solo una coincidenza che non ricevi più errori del linker, che probabilmente è solo il risultato di come hai ricostruito la libreria.
Ross Ridge,

@ RossRidge, oh sì, potrebbe essere stato proprio quello. Ah bene.
RockFrenzy

0

Ciò può accadere quando ci si collega a msvcrt.dll anziché a msvcr10.dll (o simile), che è un buon piano. Perché ti consentirà di ridistribuire la libreria runtime di Visual Studio all'interno del pacchetto software finale.

Questa soluzione alternativa mi aiuta (in 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

Questo frammento non è necessario per Visual Studio 6 e il relativo compilatore. Pertanto il #ifdef.


0

Per portare più confusione in questo thread già ricco, mi è capitato di imbattermi nello stesso esterno irrisolto su fprintf

main.obj : error LNK2019: unresolved external symbol __imp__fprintf referenced in function _GenerateInfoFile

Anche se nel mio caso era in un contesto piuttosto diverso: sotto Visual Studio 2005 (Visual Studio 8.0) e l'errore stava accadendo nel mio codice (lo stesso che stavo compilando), non in una terza parte.

È successo che questo errore è stato attivato dall'opzione / MD nei flag del mio compilatore. Il passaggio a / MT ha rimosso il problema. Questo è strano perché di solito, il collegamento statico (MT) solleva più problemi che dinamicamente (MD) ... ma nel caso in cui ne serva altro, lo metto lì.


0

Nel mio caso, questo errore proviene dalla mia prova per rimuovere le dipendenze dalla DLL della libreria di runtime dipendente dalla versione MSVC (msvcr10.dll o giù di lì) e / o rimuovere anche la libreria di runtime statica, per rimuovere il grasso in eccesso dai miei eseguibili.

Quindi uso / NODEFAULTLIB linker switch, il mio "msvcrt-light.lib" fatto da me (google per questo quando serve) e mainCRTStartup()/ WinMainCRTStartup()entry.

È IMHO da Visual Studio 2015, quindi sono rimasto fedele ai compilatori più vecchi.

Tuttavia, la definizione del simbolo _NO_CRT_STDIO_INLINE rimuove tutti i problemi e una semplice applicazione "Hello World" è di nuovo 3 KB piccola e non dipende da DLL insolite. Testato in Visual Studio 2017.


-2

Incolla questo codice in uno dei tuoi file sorgente e ricostruiscilo. Ha funzionato per me!

#include stdio.h

FILE _iob [3];

FILE * __cdecl __iob_func (void) {

_iob [0] = * stdin;

_iob [0] = * stdout;

_iob [0] = * stderr;

return _iob;

}


dovresti aggiungere la formattazione con `` e anche alcune spiegazioni
Antonin GAVREL

Sebbene questo codice possa risolvere la domanda, inclusa una spiegazione di come e perché questo risolve il problema aiuterebbe davvero a migliorare la qualità del tuo post e probabilmente si tradurrebbe in più voti positivi. Ricorda che stai rispondendo alla domanda per i lettori in futuro, non solo alla persona che chiede ora. Si prega di modificare la risposta per aggiungere spiegazioni e dare un'indicazione di ciò si applicano le limitazioni e le assunzioni.
Brian
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.