Comme indiqué ci-dessus, la bonne réponse est de tout compiler avec VS2015, mais pour l'intérêt, voici mon analyse du problème.
Ce symbole ne semble pas être défini dans une bibliothèque statique fournie par Microsoft dans le cadre de VS2015, ce qui est assez particulier puisque tous les autres le sont. Pour découvrir pourquoi, nous devons examiner la déclaration de cette fonction et, plus important encore, comment elle est utilisée.
Voici un extrait des en-têtes 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])
Nous pouvons donc voir que le travail de la fonction est de renvoyer le début d'un tableau d'objets FILE (pas de handles, le "FILE *" est le handle, FILE est la structure de données opaque sous-jacente stockant les goodies d'état importants). Les utilisateurs de cette fonction sont les trois macros stdin, stdout et stderr qui sont utilisées pour divers appels de style fscanf, fprintf.
Voyons maintenant comment Visual Studio 2015 définit les mêmes choses:
_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))
L'approche a donc changé pour que la fonction de remplacement renvoie maintenant le descripteur de fichier plutôt que l'adresse du tableau d'objets de fichier, et les macros ont changé pour appeler simplement la fonction en passant un numéro d'identification.
Alors pourquoi ne peuvent-ils / nous fournir une API compatible? Il existe deux règles clés que Microsoft ne peut pas enfreindre en ce qui concerne leur implémentation d'origine via __iob_func:
- Il doit y avoir un tableau de trois structures FILE qui peuvent être indexées de la même manière qu'auparavant.
- La disposition structurelle de FILE ne peut pas changer.
Tout changement dans l'un ou l'autre des éléments ci-dessus signifierait que le code compilé existant lié à celui-ci irait très mal si cette API est appelée.
Voyons comment FILE a été / est défini.
Tout d'abord la définition du fichier VS2008:
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
Et maintenant la définition du fichier VS2015:
typedef struct _iobuf
{
void* _Placeholder;
} FILE;
Il y a donc le nœud du problème: la structure a changé de forme. Le code compilé existant faisant référence à __iob_func repose sur le fait que les données renvoyées sont à la fois un tableau qui peut être indexé et que dans ce tableau les éléments sont à la même distance l'un de l'autre.
Les solutions possibles mentionnées dans les réponses ci-dessus dans ce sens ne fonctionneraient pas (si elles étaient appelées) pour plusieurs raisons:
FILE _iob[] = {*stdin, *stdout, *stderr};
extern "C" FILE * __cdecl __iob_func(void)
{
return _iob;
}
Le tableau FILE _iob serait compilé avec VS2015 et serait donc présenté comme un bloc de structures contenant un void *. En supposant un alignement de 32 bits, ces éléments seraient séparés de 4 octets. Donc _iob [0] est à l'offset 0, _iob [1] est à l'offset 4 et _iob [2] est à l'offset 8. Le code appelant s'attendra à la place à ce que FILE soit beaucoup plus long, aligné à 32 octets sur mon système, et ainsi il prendra l'adresse du tableau retourné et ajoutera 0 octet pour arriver à l'élément zéro (celui-là est correct), mais pour _iob [1] il en déduira qu'il a besoin d'ajouter 32 octets et pour _iob [2] il en déduira qu'il a besoin d'ajouter 64 octets (car c'est à quoi cela ressemblait dans les en-têtes VS2008). Et en effet, le code désassemblé pour VS2008 le démontre.
Un problème secondaire avec la solution ci-dessus est qu'elle copie le contenu de la structure FILE (* stdin), et non le handle FILE *. Ainsi, tout code VS2008 examinerait une structure sous-jacente différente de VS2015. Cela pourrait fonctionner si la structure ne contenait que des pointeurs, mais c'est un gros risque. Dans tous les cas, le premier problème rend cela inutile.
Le seul hack que j'ai pu imaginer est celui dans lequel __iob_func parcourt la pile d'appels pour déterminer le descripteur de fichier réel qu'ils recherchent (en fonction du décalage ajouté à l'adresse renvoyée) et renvoie une valeur calculée telle qu'elle donne la bonne réponse. C'est tout aussi insensé que cela puisse paraître, mais le prototype pour x86 uniquement (pas x64) est répertorié ci-dessous pour votre amusement. Cela a bien fonctionné dans mes expériences, mais votre kilométrage peut varier - non recommandé pour une utilisation en production!
#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;
}