Conforme respondido acima, a resposta certa é compilar tudo com o VS2015, mas por interesse o seguinte é minha análise do problema.
Este símbolo não parece ser definido em nenhuma biblioteca estática fornecida pela Microsoft como parte do VS2015, o que é bastante peculiar, pois todos os outros são. Para descobrir por que, precisamos olhar a declaração dessa função e, mais importante, como ela é usada.
Aqui está um snippet dos cabeçalhos do Visual Studio 2008:
_CRTIMP FILE * __cdecl __iob_func(void);
#define stdin (&__iob_func()[0])
#define stdout (&__iob_func()[1])
#define stderr (&__iob_func()[2])
Portanto, podemos ver que o trabalho da função é retornar o início de um array de objetos FILE (não manipuladores, o "FILE *" é o identificador, FILE é a estrutura de dados opaca subjacente que armazena os goodies de estado importantes). Os usuários desta função são as três macros stdin, stdout e stderr que são usadas para várias chamadas de estilo fscanf e fprintf.
Agora vamos dar uma olhada em como o Visual Studio 2015 define as mesmas coisas:
_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))
Portanto, a abordagem mudou para a função de substituição para agora retornar o identificador de arquivo em vez do endereço da matriz de objetos de arquivo, e as macros mudaram para simplesmente chamar a função passando um número de identificação.
Então, por que eles / nós não podemos fornecer uma API compatível? Existem duas regras principais que a Microsoft não pode violar em termos de sua implementação original via __iob_func:
- Deve haver uma matriz de três estruturas FILE que podem ser indexadas da mesma maneira que antes.
- O layout estrutural de FILE não pode ser alterado.
Qualquer mudança em qualquer uma das opções acima significaria código compilado existente vinculado a ele que iria muito mal se a API fosse chamada.
Vamos dar uma olhada em como FILE foi / é definido.
Primeiro, a definição de FILE do VS2008:
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
E agora a definição do arquivo VS2015:
typedef struct _iobuf
{
void* _Placeholder;
} FILE;
Portanto, este é o ponto crucial: a estrutura mudou de forma. O código compilado existente referindo-se a __iob_func se baseia no fato de que os dados retornados são uma matriz que pode ser indexada e que nessa matriz os elementos estão à mesma distância.
As possíveis soluções mencionadas nas respostas acima ao longo dessas linhas não funcionariam (se chamadas) por alguns motivos:
FILE _iob[] = {*stdin, *stdout, *stderr};
extern "C" FILE * __cdecl __iob_func(void)
{
return _iob;
}
O array FILE _iob seria compilado com VS2015 e, portanto, seria apresentado como um bloco de estruturas contendo um vazio *. Assumindo o alinhamento de 32 bits, esses elementos estariam separados por 4 bytes. Portanto, _iob [0] está no deslocamento 0, _iob [1] está no deslocamento 4 e _iob [2] está no deslocamento 8. Em vez disso, o código de chamada esperará que FILE seja muito mais longo, alinhado a 32 bytes em meu sistema, e assim pegará o endereço do array retornado e adicionará 0 bytes para chegar ao elemento zero (aquele está certo), mas para _iob [1] ele irá deduzir que precisa adicionar 32 bytes e para _iob [2] irá deduzir que ele precisa adicionar 64 bytes (porque é assim que parecia nos cabeçalhos do VS2008). E de fato o código desmontado para VS2008 demonstra isso.
Um problema secundário com a solução acima é que ela copia o conteúdo da estrutura FILE (* stdin), não o identificador FILE *. Portanto, qualquer código do VS2008 estaria olhando para uma estrutura subjacente diferente do VS2015. Isso pode funcionar se a estrutura contiver apenas ponteiros, mas é um grande risco. Em qualquer caso, a primeira questão torna isso irrelevante.
O único hack que consegui imaginar é aquele em que __iob_func percorre a pilha de chamadas para descobrir qual identificador de arquivo real eles estão procurando (com base no deslocamento adicionado ao endereço retornado) e retorna um valor calculado de modo que dá a resposta certa. Isso é tão insano quanto parece, mas o protótipo para x86 apenas (não x64) está listado abaixo para sua diversão. Funcionou bem em meus experimentos, mas sua milhagem pode variar - não é recomendado para uso em produção!
#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;
}