As answered above, the right answer is to compile everything with VS2015, but for interest the following is my analysis of the problem.
This symbol does not appear to be defined in any static library provided by Microsoft as part of VS2015, which is rather peculiar since all others are. To discover why, we need to look at the declaration of that function and, more importantly, how it's used.
Here's a snippet from the Visual Studio 2008 headers:
_CRTIMP FILE * __cdecl __iob_func(void);
#define stdin (&__iob_func()[0])
#define stdout (&__iob_func()[1])
#define stderr (&__iob_func()[2])
So we can see that the job of the function is to return the start of an array of FILE objects (not handles, the "FILE *" is the handle, FILE is the underlying opaque data structure storing the important state goodies). The users of this function are the three macros stdin, stdout and stderr which are used for various fscanf, fprintf style calls.
Now let's take a look at how Visual Studio 2015 defines the same things:
_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))
So the approach has changed for the replacement function to now return the file handle rather than the address of the array of file objects, and the macros have changed to simply call the function passing in an identifying number.
So why can't they/we provide a compatible API?
There are two key rules which Microsoft can't contravene in terms of their original implementation via __iob_func:
- There must be an array of three FILE structures which can be indexed in the same manner as before.
- The structural layout of FILE cannot change.
Any change in either of the above would mean existing compiled code linked against that would go badly wrong if that API is called.
Let's take a look at how FILE was/is defined.
First the VS2008 FILE definition:
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
And now the VS2015 FILE definition:
typedef struct _iobuf
{
void* _Placeholder;
} FILE;
So there is the crux of it: the structure has changed shape. Existing compiled code referring to __iob_func relies upon the fact that the data returned is both an array that can be indexed and that in that array the elements are the same distance apart.
The possible solutions mentioned in the answers above along these lines would not work (if called) for a few reasons:
FILE _iob[] = {*stdin, *stdout, *stderr};
extern "C" FILE * __cdecl __iob_func(void)
{
return _iob;
}
The FILE array _iob would be compiled with VS2015 and so it would be laid out as a block of structures containing a void*. Assuming 32-bit alignment, these elements would be 4 bytes apart. So _iob[0] is at offset 0, _iob[1] is at offset 4 and _iob[2] is at offset 8. The calling code will instead expect FILE to be much longer, aligned at 32 bytes on my system, and so it will take the address of the returned array and add 0 bytes to get to element zero (that one is okay), but for _iob[1] it will deduce that it needs to add 32 bytes and for _iob[2] it will deduce that it needs to add 64-bytes (because that's how it looked in the VS2008 headers). And indeed the disassembled code for VS2008 demonstrates this.
A secondary issue with the above solution is that it copies the content of the FILE structure (*stdin), not the FILE * handle. So any VS2008 code would be looking at a different underlying structure to VS2015. This might work if the structure only contained pointers, but that's a big risk. In any case the first issue renders this irrelevant.
The only hack I've been able to dream up is one in which __iob_func walks the call stack to work out which actual file handle they are looking for (based on the offset added to the returned address) and returns a computed value such that it gives the right answer. This is every bit as insane as it sounds, but the prototype for x86 only (not x64) is listed below for your amusement. It worked okay in my experiments, but your mileage may vary - not recommended for production use!
#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;
}