Wie oben beantwortet, ist die richtige Antwort, alles mit VS2015 zu kompilieren, aber aus Interesse ist das Folgende meine Analyse des Problems.
Dieses Symbol scheint in keiner statischen Bibliothek definiert zu sein, die von Microsoft als Teil von VS2015 bereitgestellt wird, was ziemlich eigenartig ist, da alle anderen es sind. Um herauszufinden, warum, müssen wir uns die Deklaration dieser Funktion und vor allem ihre Verwendung ansehen.
Hier ist ein Ausschnitt aus den Visual Studio 2008-Headern:
_CRTIMP FILE * __cdecl __iob_func(void);
#define stdin (&__iob_func()[0])
#define stdout (&__iob_func()[1])
#define stderr (&__iob_func()[2])
Wir können also sehen, dass die Aufgabe der Funktion darin besteht, den Anfang eines Arrays von FILE-Objekten zurückzugeben (keine Handles, "FILE *" ist das Handle, FILE ist die zugrunde liegende undurchsichtige Datenstruktur, in der die wichtigen Status-Goodies gespeichert sind). Die Benutzer dieser Funktion sind die drei Makros stdin, stdout und stderr, die für verschiedene Aufrufe im fscanf- und fprintf-Stil verwendet werden.
Schauen wir uns nun an, wie Visual Studio 2015 dieselben Dinge definiert:
_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))
Daher hat sich der Ansatz geändert, dass die Ersetzungsfunktion jetzt das Dateihandle anstelle der Adresse des Arrays von Dateiobjekten zurückgibt, und die Makros wurden geändert, um einfach die Funktion aufzurufen, die eine Identifikationsnummer übergibt.
Warum können sie / wir keine kompatible API bereitstellen? Es gibt zwei wichtige Regeln, gegen die Microsoft bei der ursprünglichen Implementierung über __iob_func nicht verstoßen kann:
- Es muss ein Array von drei FILE-Strukturen vorhanden sein, die auf die gleiche Weise wie zuvor indiziert werden können.
- Das strukturelle Layout von FILE kann sich nicht ändern.
Jede Änderung in einem der oben genannten Punkte würde bedeuten, dass vorhandener kompilierter Code, der mit diesem verknüpft ist, beim Aufruf dieser API schlecht funktioniert.
Werfen wir einen Blick darauf, wie FILE definiert wurde / wird.
Zuerst die VS2008-DATEI-Definition:
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
Und jetzt die Definition der VS2015-DATEI:
typedef struct _iobuf
{
void* _Placeholder;
} FILE;
Da ist also der springende Punkt: Die Struktur hat ihre Form geändert. Vorhandener kompilierter Code, der sich auf __iob_func bezieht, beruht auf der Tatsache, dass die zurückgegebenen Daten sowohl ein Array sind, das indiziert werden kann, als auch dass die Elemente in diesem Array den gleichen Abstand voneinander haben.
Die möglichen Lösungen, die in den obigen Antworten in diesem Sinne erwähnt werden, würden aus einigen Gründen nicht funktionieren (wenn sie aufgerufen werden):
FILE _iob[] = {*stdin, *stdout, *stderr};
extern "C" FILE * __cdecl __iob_func(void)
{
return _iob;
}
Das FILE-Array _iob würde mit VS2015 kompiliert und daher als Strukturblock mit einer Leere * angelegt. Unter der Annahme einer 32-Bit-Ausrichtung wären diese Elemente 4 Byte voneinander entfernt. Also ist _iob [0] auf Offset 0, _iob [1] auf Offset 4 und _iob [2] auf Offset 8. Der aufrufende Code erwartet stattdessen, dass FILE viel länger ist und auf meinem System mit 32 Bytes ausgerichtet ist, und so weiter Es nimmt die Adresse des zurückgegebenen Arrays und addiert 0 Bytes, um zum Element Null zu gelangen (dieses ist in Ordnung), aber für _iob [1] wird abgeleitet, dass 32 Bytes hinzugefügt werden müssen, und für _iob [2] wird abgeleitet dass es 64 Bytes hinzufügen muss (weil es in den VS2008-Headern so aussah). Und tatsächlich zeigt der zerlegte Code für VS2008 dies.
Ein sekundäres Problem bei der obigen Lösung besteht darin, dass der Inhalt der FILE-Struktur (* stdin) kopiert wird , nicht das FILE * -Handle. Jeder VS2008-Code würde also eine andere zugrunde liegende Struktur als VS2015 betrachten. Dies könnte funktionieren, wenn die Struktur nur Zeiger enthält, aber das ist ein großes Risiko. In jedem Fall macht die erste Ausgabe dies irrelevant.
Der einzige Hack, den ich mir ausdenken konnte, ist ein Hack, bei dem __iob_func den Aufrufstapel durchläuft, um herauszufinden, nach welchem tatsächlichen Dateihandle sie suchen (basierend auf dem Offset, der der zurückgegebenen Adresse hinzugefügt wurde), und einen berechneten Wert zurückgibt, so dass dieser gibt die richtige Antwort. Dies ist genauso verrückt wie es sich anhört, aber der Prototyp nur für x86 (nicht x64) ist unten aufgeführt, um Sie zu unterhalten. In meinen Experimenten hat es gut funktioniert, aber Ihr Kilometerstand kann variieren - nicht für den Produktionsbetrieb empfohlen!
#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;
}