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:
- Deve esserci un array di tre strutture FILE che possono essere indicizzate nello stesso modo di prima.
- 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;
}