símbolo externo no resuelto __imp__fprintf y __imp____iob_func, SDL2

108

¿Alguien podría explicar qué

__imp__fprintf

y

__imp____iob_func

medios externos no resueltos?

Porque obtengo estos errores cuando intento compilar:

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

Ya puedo decir que el problema no es de vincular mal. He vinculado todo correctamente, pero por alguna razón no se compilará.

Estoy intentando utilizar SDL2.

Estoy usando Visual Studio 2015 como compilador.

Me he vinculado a SDL2.lib y SDL2main.lib en Linker -> Entrada -> Dependencias adicionales y me he asegurado de que los directorios de VC ++ sean correctos.

RockFrenzy
fuente
1
¿Podría demostrarlo mostrando la configuración de su vinculador, por favor?
πάντα ῥεῖ
@ πάνταῥεῖ, he vinculado a SDL2.lib y SDL2main.lib en la configuración del vinculador de entrada y me he asegurado de que los directorios apunten al directorio correcto.
RockFrenzy
1
posible duplicado del error LNK2001 __imp_fprintf Visual Studio 2015 RC
eliminado

Respuestas:

123

¡Finalmente he descubierto por qué está sucediendo esto!

En visual studio 2015, stdin, stderr, stdout se definen de la siguiente manera:

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

Pero anteriormente, se definieron como:

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

Así que ahora __iob_func ya no está definido, lo que conduce a un error de enlace cuando se usa un archivo .lib compilado con versiones anteriores de Visual Studio.

Para resolver el problema, puede intentar definir __iob_func()usted mismo cuál debería devolver una matriz que contenga {*stdin,*stdout,*stderr}.

Con respecto a los otros errores de enlace sobre las funciones de stdio (en mi caso lo fue sprintf()), puede agregar legacy_stdio_definitions.lib a sus opciones de vinculador.

Dios
fuente
1
Gracias por rastrear esto. IIRC el problema con {* stdin, * stdout, * stderr} podría ser que diferentes unidades de compilación podrían tener su 'propia' copia de stdin, razón por la cual estas funciones fueron llamadas directamente.
Steven R. Loomis
3
Eso también me resolvió, solo un recordatorio para usar extern "C"en la declaración / definición.
Vargas
4
¿Alguien puede escribir exactamente cómo debería verse la función de reemplazo? Probé diferentes variantes y sigo recibiendo errores de compilación. Gracias.
Milan Babuškov
55
extern "C" { FILE __iob_func[3] = { *stdin,*stdout,*stderr }; }
PoL0
1
La definición de iob_func anterior no funciona, consulte la respuesta de MarkH para obtener una definición correcta. (No puede simplemente definir una función como una matriz y esperar que las llamadas funcionen).
Hans Olsson
59

Para Milan Babuškov, en mi opinión, así es exactamente como debería verse la función de reemplazo :-)

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

extern "C" FILE * __cdecl __iob_func(void)
{
    return _iob;
}
inteligente
fuente
5
Solo falta un #ifdef para MSVC y para la versión de
MSVC
1
Como señala MarkH en otra respuesta que parece correcta, pero no funcionará.
Hans Olsson
4
@paulm, creo que te refieres #if defined(_MSC_VER) && (_MSC_VER >= 1900).
Jesse Chisholm
@JesseChisholm quizás, depende si esto también se aplica a todas las versiones futuras conocidas de MSVC o no;)
Paulm
42

Microsoft tiene una nota especial sobre esto ( https://msdn.microsoft.com/en-us/library/bb531344.aspx#BK_CRT ):

La familia de funciones printf y scanf ahora se definen en línea.

Las definiciones de todas las funciones printf y scanf se han movido en línea a stdio.h , conio.h y otros encabezados CRT. Este es un cambio importante que conduce a un error de vinculador (LNK2019, símbolo externo no resuelto) para cualquier programa que declaró estas funciones localmente sin incluir los encabezados CRT apropiados. Si es posible, debe actualizar el código para incluir los encabezados CRT (es decir, agregar #include) y las funciones en línea, pero si no desea modificar su código para incluir estos archivos de encabezado, una solución alternativa es agregar un library a la entrada del enlazador, legacy_stdio_definitions.lib .

Para agregar esta biblioteca a la entrada del vinculador en el IDE, abra el menú contextual para el nodo del proyecto, elija Propiedades, luego en el cuadro de diálogo Propiedades del proyecto, elija Vinculador y edite la Entrada del vinculador para agregar legacy_stdio_definitions.lib al punto y coma -lista separada.

Si su proyecto se vincula con bibliotecas estáticas que se compilaron con una versión de Visual C ++ anterior a 2015, el vinculador puede informar un símbolo externo no resuelto. Estos errores pueden hacer referencia a definiciones de stdio internas para _iob , _iob_func o importaciones relacionadas para determinadas funciones de stdio en forma de __imp_ *. Microsoft recomienda que vuelva a compilar todas las bibliotecas estáticas con la última versión del compilador y las bibliotecas de Visual C ++ cuando actualice un proyecto. Si la biblioteca es una biblioteca de terceros para la cual la fuente no está disponible, debe solicitar un binario actualizado al tercero o encapsular su uso de esa biblioteca en una DLL separada que compile con la versión anterior del compilador de Visual C ++. y bibliotecas.

Kingsb
fuente
7
O #pragma comment(lib, "legacy_stdio_definitions.lib"), pero esto no soluciona el problema __imp___iob_func, ¿hay una biblioteca heredada para esto también?
bytecode77
29

Como se respondió anteriormente, la respuesta correcta es compilar todo con VS2015, pero por interés, el siguiente es mi análisis del problema.

Este símbolo no parece estar definido en ninguna biblioteca estática proporcionada por Microsoft como parte de VS2015, lo cual es bastante peculiar ya que todos los demás lo son. Para descubrir por qué, necesitamos mirar la declaración de esa función y, lo que es más importante, cómo se usa.

Aquí hay un fragmento de los encabezados de Visual Studio 2008:

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

Así que podemos ver que el trabajo de la función es devolver el inicio de una matriz de objetos FILE (no identificadores, el "FILE *" es el identificador, FILE es la estructura de datos opaca subyacente que almacena las ventajas importantes del estado). Los usuarios de esta función son las tres macros stdin, stdout y stderr que se utilizan para varias llamadas de estilo fscanf, fprintf.

Ahora echemos un vistazo a cómo Visual Studio 2015 define las mismas cosas:

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

Entonces, el enfoque ha cambiado para que la función de reemplazo ahora devuelva el identificador del archivo en lugar de la dirección de la matriz de objetos de archivo, y las macros han cambiado para simplemente llamar a la función pasando un número de identificación.

Entonces, ¿por qué no podemos proporcionar una API compatible? Hay dos reglas clave que Microsoft no puede contravenir en términos de su implementación original a través de __iob_func:

  1. Debe haber una matriz de tres estructuras FILE que se puedan indexar de la misma manera que antes.
  2. El diseño estructural de FILE no puede cambiar.

Cualquier cambio en cualquiera de los anteriores significaría que el código compilado existente vinculado con eso saldría muy mal si se llama a esa API.

Echemos un vistazo a cómo se definió FILE.

Primero, la definición de ARCHIVO VS2008:

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

Y ahora la definición de ARCHIVO VS2015:

typedef struct _iobuf
{
    void* _Placeholder;
} FILE;

Así que ahí está el quid de la cuestión: la estructura ha cambiado de forma. El código compilado existente que hace referencia a __iob_func se basa en el hecho de que los datos devueltos son tanto una matriz que se puede indexar como que en esa matriz los elementos están separados por la misma distancia.

Las posibles soluciones mencionadas en las respuestas anteriores a lo largo de estas líneas no funcionarían (si se llaman) por algunas razones:

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

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

La matriz FILE _iob se compilaría con VS2015 y, por lo tanto, se presentaría como un bloque de estructuras que contiene un vacío *. Suponiendo una alineación de 32 bits, estos elementos estarían separados por 4 bytes. Entonces _iob [0] está en el desplazamiento 0, _iob [1] está en el desplazamiento 4 y _iob [2] está en el desplazamiento 8. En cambio, el código de llamada esperará que FILE sea mucho más largo, alineado a 32 bytes en mi sistema, y ​​así tomará la dirección de la matriz devuelta y agregará 0 bytes para llegar al elemento cero (ese está bien), pero para _iob [1] deducirá que necesita agregar 32 bytes y para _iob [2] deducirá que necesita agregar 64 bytes (porque así es como se veía en los encabezados VS2008). Y de hecho, el código desmontado de VS2008 lo demuestra.

Un problema secundario con la solución anterior es que copia el contenido de la estructura FILE (* stdin), no el identificador FILE *. Por lo tanto, cualquier código VS2008 buscaría una estructura subyacente diferente a VS2015. Esto podría funcionar si la estructura solo contuviera punteros, pero eso es un gran riesgo. En cualquier caso, la primera cuestión hace que esto sea irrelevante.

El único truco que he podido soñar es uno en el que __iob_func recorre la pila de llamadas para averiguar qué identificador de archivo real están buscando (según el desplazamiento agregado a la dirección devuelta) y devuelve un valor calculado tal que da la respuesta correcta. Esto es tan loco como parece, pero el prototipo solo para x86 (no x64) se enumera a continuación para su diversión. Funcionó bien en mis experimentos, pero su kilometraje puede variar, ¡no se recomienda para uso en producción!

#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;
}
MarkH
fuente
La respuesta correcta es esta, la solución más sencilla es actualizar el proyecto a VS2015 y luego compilarlo.
Akumaburn
2
En mi caso, necesito actualizar muchos proyectos (proyectos C ++ y C #) de Visual Studio 2013 para usar Visual Studio 2015 Update 3. Quiero mantener VC100 (compilador Visual Studio 2010 C ++) cuando construyo proyectos C ++ pero tengo los mismos errores como anteriormente. Me fijo imp _fprintf añadiendo legacy_stdio_definitions.lib al enlazador. ¿Cómo puedo solucionar también _imp____iob_func ?
Mohamed BOUZIDI
Antes de responder a mi pregunta anterior, ¿es normal que se produzcan estos errores al usar msbuild 14 e IntelCompiler 2016 y VC100 para compilar proyectos C ++?
Mohamed BOUZIDI
1
¿Qué puedo hacer para una compilación x64?
athos
28

Tuve el mismo problema en VS2015. Lo resolví compilando las fuentes SDL2 en VS2015.

  1. Vaya a http://libsdl.org/download-2.0.php y descargue el código fuente de SDL 2.
  2. Abra SDL_VS2013.sln en VS2015 . Se le pedirá que convierta los proyectos. Hazlo.
  3. Compile el proyecto SDL2.
  4. Compile el proyecto SDL2main.
  5. Utilice los nuevos archivos de salida generados SDL2main.lib, SDL2.lib y SDL2.dll en su proyecto SDL 2 en VS2015.

fuente
4
Por cierto, la compilación de SDL 2.0.3 requiere la instalación de DirectX SDK de junio de 2010.
Joe
1
Funcionó para mí, gracias !! Pero solo necesitaba compilar SDL2mainy copiarSDL2main.lib
kgwong
10

No se porque pero:

#ifdef main
#undef main
#endif

Después de la inclusión, pero antes de su principal, debería solucionarlo según mi experiencia.

El XGood
fuente
1
.... Bien .... así que antes de intentar esto, me dije en voz alta que de alguna manera dudo que esto vaya a funcionar, pero que funcionó totalmente ... ¿puede explicar por qué esto funciona ...?
Trevor Hart
1
@TrevorHart Creo que no define un SDL principal "defectuoso" que incluye referencias a los búferes "indefinidos" y si el tuyo usa tus búferes, entonces funciona muy bien.
The XGood
2
Este es un truco de malas prácticas horribles, pero son 3 líneas y funciona y me salvó de tener que hacer un rabbithole para construir SDL, así que ... muy bien hecho.
Cheezmeister
1
@Cheezmeister A menudo se requieren malas prácticas y trucos para todo. Especialmente las cosas que no deberían necesitarlas.
The XGood
7

Vincular significa no funcionar correctamente. Profundizando en stdio.h de VS2012 y VS2015, lo siguiente funcionó para mí. Por desgracia, tienes que decidir si debería funcionar para uno de {stdin, stdout, stderr}, nunca más de 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);
}
Volker
fuente
7

Mi consejo es no (intentar) implementar __iob_func.

Mientras corrige estos errores:

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

Probé las soluciones de las otras respuestas, pero al final, devolver una FILE*matriz C no coincide con una matriz de estructuras IOB internas de Windows. @Volker tiene razón en que nunca funcionará para más de uno de stdin, stdouto stderr.

Si una biblioteca UTILIZA uno de esos flujos, se bloqueará . Siempre que su programa no haga que lib los use, nunca lo sabrá . Por ejemplo, png_default_errorescribe en stderrcuando el CRC no coincide en los metadatos del PNG. (Normalmente no es un problema digno de un accidente)

Conclusión: No es posible mezclar las bibliotecas VS2012 (Platform Toolset v110 / v110_xp) y VS2015 +, si usan stdin, stdout y / o stderr.

Solución: vuelva a compilar sus bibliotecas que tienen __iob_funcsímbolos sin resolver con su versión actual de VS y un conjunto de herramientas de plataforma correspondiente.

Luc Bloom
fuente
2

Resuelvo este problema con la siguiente función. Yo uso Visual Studio 2019.

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

debido a que la llamada a la función definida por macro stdin, la expresión "* stdin" no se puede utilizar como inicializador de matriz global. Pero el inicializador de matriz local es posible. lo siento, soy pobre en inglés.

soen.kr
fuente
1

Para cualquiera que todavía esté buscando una respuesta donde los trucos anteriores no funcionaron. La vinculación estática es la forma de resolver este problema. Cambie la configuración de la biblioteca en tiempo de ejecución como se muestra a continuación

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

Sisir
fuente
Aquí hay una discusión sobre esta solución: social.msdn.microsoft.com/Forums/vstudio/en-US/…
Sisir
0

Me las arreglé para solucionar el problema.

La fuente del error fue esta línea de código, que se puede encontrar en el código fuente de SDLmain.

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

Entonces, lo que hice fue editar el código fuente en SDLmain de esa línea también:

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

Y luego construí SDLmain y copié y reemplacé el antiguo SDLmain.lib en mi directorio de biblioteca SDL2 con el recién construido y editado.

Luego, cuando ejecuté mi programa con SDL2, no aparecieron mensajes de error y el código se ejecutó sin problemas.

No sé si esto me morderá más tarde, pero todo va muy bien.

RockFrenzy
fuente
Su cambio es un error en sí mismo y no habría solucionado el problema descrito en su pregunta. Es solo una coincidencia que ya no esté recibiendo errores del vinculador, que probablemente sea únicamente el resultado de cómo reconstruyó la biblioteca.
Ross Ridge
@RossRidge, oh sí, eso podría haber sido eso. Ah bueno.
RockFrenzy
0

Esto puede suceder cuando se vincula a msvcrt.dll en lugar de msvcr10.dll (o similar), lo cual es un buen plan. Porque lo liberará para redistribuir la biblioteca en tiempo de ejecución de Visual Studio dentro de su paquete de software final.

Esa solución me ayuda (en 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

Este fragmento no es necesario para Visual Studio 6 y su compilador. Por lo tanto, el #ifdef.

usuario2699548
fuente
0

Para traer más confusión en este hilo ya rico, me topé con el mismo externo no resuelto en fprintf

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

Incluso si en mi caso fue en un contexto bastante diferente: bajo Visual Studio 2005 (Visual Studio 8.0) y el error estaba ocurriendo en mi propio código (el mismo que estaba compilando), no en un tercero.

Sucedió que este error fue provocado por la opción / MD en mis banderas del compilador. Cambiar a / MT eliminó el problema. Esto es extraño porque, por lo general, la vinculación estática (MT) plantea más problemas que dinámicamente (MD) ... pero en caso de que sirva a otros, lo puse allí.

Circonio
fuente
0

En mi caso, este error proviene de mi prueba para eliminar las dependencias de la biblioteca de tiempo de ejecución dependiente de la versión de MSVC (msvcr10.dll más o menos) y / o eliminar también la biblioteca de tiempo de ejecución estática, para eliminar el exceso de grasa de mis ejecutables.

Así que uso el modificador de enlace / NODEFAULTLIB, mi "msvcrt-light.lib" hecho por mí mismo (busque en Google cuando lo necesite), y mainCRTStartup()/WinMainCRTStartup() entradas .

Es en mi humilde opinión desde Visual Studio 2015, así que me limité a los compiladores más antiguos.

Sin embargo, definir el símbolo _NO_CRT_STDIO_INLINE elimina todas las molestias, y una aplicación simple "Hello World" es nuevamente 3 KB pequeña y no depende de DLL inusuales. Probado en Visual Studio 2017.

Henrik Haftmann
fuente
-2

Pegue este código en cualquiera de sus archivos fuente y vuelva a compilar. Trabajó para mi !

#include stdio.h

ARCHIVO _iob [3];

ARCHIVO * __cdecl __iob_func (void) {

_iob [0] = * stdin;

_iob [0] = * stdout;

_iob [0] = * stderr;

return _iob;

}

Patel profundo
fuente
debe agregar formateo con `` `y también algunas explicaciones
Antonin GAVREL
Si bien este código puede resolver la pregunta, incluir una explicación de cómo y por qué esto resuelve el problema realmente ayudaría a mejorar la calidad de su publicación y probablemente resultaría en más votos a favor. Recuerde que está respondiendo la pregunta a los lectores en el futuro, no solo a la persona que pregunta ahora. Por favor, editar su respuesta para agregar explicaciones y dar una indicación de lo que se aplican limitaciones y supuestos.
Brian