上記で答えたように、正しい答えはVS2015ですべてをコンパイルすることですが、興味のある方は、問題の私の分析を以下に示します。
このシンボルは、VS2015の一部としてMicrosoftが提供する静的ライブラリで定義されているようには見えません。理由を見つけるには、その関数の宣言を確認する必要があります。さらに重要なのは、その使用方法です。
Visual Studio 2008ヘッダーのスニペットを次に示します。
_CRTIMP FILE * __cdecl __iob_func(void);
#define stdin (&__iob_func()[0])
#define stdout (&__iob_func()[1])
#define stderr (&__iob_func()[2])
したがって、関数の仕事はFILEオブジェクトの配列の開始を返すことであることがわかります(ハンドルではなく、「FILE *」はハンドル、FILEは重要な状態の良いものを格納する基になる不透明なデータ構造です)。この関数のユーザーは、さまざまなfscanf、fprintfスタイルの呼び出しに使用される3つのマクロstdin、stdout、およびstderrです。
次に、Visual Studio 2015が同じことをどのように定義するかを見てみましょう。
_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))
そのため、置換関数のアプローチが変更され、ファイルオブジェクトの配列のアドレスではなくファイルハンドルが返されるようになりました。マクロは、識別番号を渡す関数を呼び出すだけに変更されました。
では、なぜ互換性のあるAPIを提供できないのでしょうか。Microsoftが__iob_funcを介した元の実装の点で違反できない2つの主要なルールがあります。
- 以前と同じ方法でインデックスを付けることができる3つのFILE構造の配列が必要です。
- FILEの構造レイアウトは変更できません。
上記のいずれかの変更は、そのAPIが呼び出された場合にリンクされている既存のコンパイル済みコードがひどく間違っていることを意味します。
FILEがどのように定義されているかを見てみましょう。
最初にVS2008 FILE定義:
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
そして今、VS2015 FILE定義:
typedef struct _iobuf
{
void* _Placeholder;
} FILE;
つまり、その核心部分があります。構造が形を変えました。__iob_funcを参照する既存のコンパイル済みコードは、返されるデータがインデックス付け可能な配列であり、その配列内の要素が同じ距離離れているという事実に依存しています。
これらの行に沿って上記の回答で言及された可能な解決策は、いくつかの理由で(呼び出された場合)機能しません。
FILE _iob[] = {*stdin, *stdout, *stderr};
extern "C" FILE * __cdecl __iob_func(void)
{
return _iob;
}
FILE配列_iobはVS2015でコンパイルされるため、void *を含む構造のブロックとして配置されます。32ビットアライメントと仮定すると、これらの要素は4バイト離れています。したがって、_iob [0]はオフセット0にあり、_iob [1]はオフセット4にあり、_iob [2]はオフセット8にあります。呼び出しコードは、FILEがはるかに長く、システム上で32バイトにアラインされることを期待します。返された配列のアドレスを取得して0バイトを追加し、要素0に到達します(これは問題ありません)が、_iob [1]の場合は32バイトを追加する必要があると推定され、_iob [2]の場合は推定されます64バイトを追加する必要があること(VS2008ヘッダーでこのように表示されていたため)。実際、VS2008の逆アセンブルされたコードはこれを示しています。
上記のソリューションの2番目の問題は、FILE *ハンドルではなく、FILE構造体(* stdin)の内容をコピーすることです。したがって、どのVS2008コードもVS2015とは異なる基本構造を調べます。これは、構造体にポインターのみが含まれている場合に機能する可能性がありますが、これは大きなリスクです。いずれにせよ、最初の問題はこれを無関係にしています。
私が思いついた唯一のハックは、__ iob_funcがコールスタックを調べて、探している実際のファイルハンドルを調べ(返されたアドレスに追加されたオフセットに基づいて)、次のような計算値を返します。正しい答えを与えます。これは想像以上に正気ではありませんが、娯楽のためにx86のみ(x64ではない)のプロトタイプを以下に示します。私の実験では大丈夫でしたが、走行距離は異なる場合があります-実稼働での使用はお勧めしません!
#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;
}