Như đã trả lời ở trên, câu trả lời đúng là biên dịch mọi thứ với VS2015, nhưng đối với sự quan tâm, sau đây là phân tích của tôi về vấn đề.
Biểu tượng này dường như không được xác định trong bất kỳ thư viện tĩnh nào do Microsoft cung cấp như một phần của VS2015, điều này khá kỳ lạ so với tất cả các thư viện khác. Để tìm hiểu lý do tại sao, chúng ta cần xem xét khai báo của hàm đó và quan trọng hơn là cách nó được sử dụng.
Đây là một đoạn mã từ các tiêu đề Visual Studio 2008:
_CRTIMP FILE * __cdecl __iob_func(void);
#define stdin (&__iob_func()[0])
#define stdout (&__iob_func()[1])
#define stderr (&__iob_func()[2])
Vì vậy, chúng ta có thể thấy rằng công việc của hàm là trả về phần bắt đầu của một mảng các đối tượng FILE (không phải xử lý, "FILE *" là xử lý, FILE là cấu trúc dữ liệu không rõ ràng bên dưới lưu trữ các tính năng quan trọng của trạng thái). Người dùng của hàm này là ba macro stdin, stdout và stderr được sử dụng cho các lệnh gọi kiểu fscanf, fprintf khác nhau.
Bây giờ chúng ta hãy xem cách Visual Studio 2015 xác định những thứ tương tự:
_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))
Vì vậy, cách tiếp cận đã thay đổi để hàm thay thế bây giờ trả về xử lý tệp thay vì địa chỉ của mảng đối tượng tệp và các macro đã thay đổi để chỉ cần gọi hàm truyền vào một số nhận dạng.
Vậy tại sao họ / chúng tôi không thể cung cấp một API tương thích? Có hai quy tắc chính mà Microsoft không thể trái ngược về cách triển khai ban đầu của họ thông qua __iob_func:
- Phải có một mảng gồm ba cấu trúc FILE có thể được lập chỉ mục theo cách tương tự như trước đây.
- Bố cục cấu trúc của FILE không thể thay đổi.
Bất kỳ thay đổi nào ở một trong hai điều trên có nghĩa là mã đã biên dịch hiện có được liên kết với điều đó sẽ bị sai nghiêm trọng nếu API đó được gọi.
Hãy xem cách FILE được / được định nghĩa.
Đầu tiên là định nghĩa FILE VS2008:
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
Và bây giờ là định nghĩa FILE VS2015:
typedef struct _iobuf
{
void* _Placeholder;
} FILE;
Vì vậy, mấu chốt của nó: cấu trúc đã thay đổi hình dạng. Mã đã biên dịch hiện tại đề cập đến __iob_func dựa trên thực tế là dữ liệu trả về là một mảng có thể được lập chỉ mục và trong mảng đó, các phần tử cách nhau một khoảng cách.
Các giải pháp khả thi được đề cập trong các câu trả lời ở trên dọc theo những dòng này sẽ không hoạt động (nếu được gọi) vì một số lý do:
FILE _iob[] = {*stdin, *stdout, *stderr};
extern "C" FILE * __cdecl __iob_func(void)
{
return _iob;
}
Mảng FILE _iob sẽ được biên dịch với VS2015 và do đó, nó sẽ được đặt dưới dạng một khối cấu trúc chứa khoảng trống *. Giả sử căn chỉnh 32-bit, các phần tử này sẽ cách nhau 4 byte. Vì vậy, _iob [0] ở độ lệch 0, _iob [1] ở độ lệch 4 và _iob [2] ở độ lệch 8. Thay vào đó, mã gọi sẽ mong đợi FILE dài hơn nhiều, được căn chỉnh ở 32 byte trên hệ thống của tôi, v.v. nó sẽ lấy địa chỉ của mảng được trả về và thêm 0 byte để đến phần tử 0 (cái đó được), nhưng đối với _iob [1], nó sẽ suy ra rằng nó cần thêm 32 byte và đối với _iob [2] thì nó sẽ suy ra rằng nó cần thêm 64 byte (vì đó là cách nó trông như thế nào trong tiêu đề VS2008). Và thực sự mã được tháo rời cho VS2008 thể hiện điều này.
Một vấn đề thứ hai với giải pháp trên là nó sao chép nội dung của cấu trúc FILE (* stdin), không phải xử lý FILE *. Vì vậy, bất kỳ mã VS2008 nào cũng sẽ xem xét một cấu trúc cơ bản khác với VS2015. Điều này có thể hoạt động nếu cấu trúc chỉ chứa con trỏ, nhưng đó là một rủi ro lớn. Trong mọi trường hợp, vấn đề đầu tiên làm cho điều này không liên quan.
Cách hack duy nhất mà tôi có thể mơ ước là một trong đó __iob_func đi qua ngăn xếp cuộc gọi để tìm ra xử lý tệp thực sự mà họ đang tìm kiếm (dựa trên phần bù được thêm vào địa chỉ trả về) và trả về một giá trị được tính toán sao cho nó đưa ra câu trả lời đúng. Điều này nghe có vẻ điên rồ một chút, nhưng nguyên mẫu chỉ dành cho x86 (không phải x64) được liệt kê bên dưới để bạn giải trí. Nó hoạt động ổn trong các thử nghiệm của tôi, nhưng số dặm của bạn có thể thay đổi - không được khuyến nghị sử dụng trong sản xuất!
#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;
}