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:
- Debe haber una matriz de tres estructuras FILE que se puedan indexar de la misma manera que antes.
- 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;
}