สัญลักษณ์ภายนอกที่ไม่ได้รับการแก้ไข __imp__fprintf และ __imp____iob_func, SDL2


108

ใครช่วยอธิบายว่าไฟล์

__imp__fprintf

และ

__imp____iob_func

วิธีการภายนอกที่ไม่ได้รับการแก้ไข?

เพราะฉันได้รับข้อผิดพลาดเหล่านี้เมื่อฉันพยายามรวบรวม:

1>SDL2main.lib(SDL_windows_main.obj) : error LNK2019: unresolved external symbol __imp__fprintf referenced in function _ShowError
1>SDL2main.lib(SDL_windows_main.obj) : error LNK2019: unresolved external symbol __imp____iob_func referenced in function _ShowError
1>E:\Documents\Visual Studio 2015\Projects\SDL2_Test\Debug\SDL2_Test.exe : fatal error LNK1120: 2 unresolved externals

ฉันสามารถพูดได้แล้วว่าปัญหาไม่ได้มาจากการเชื่อมโยงผิด ฉันเชื่อมโยงทุกอย่างถูกต้องแล้ว แต่ด้วยเหตุผลบางอย่างมันไม่สามารถรวบรวมได้

ฉันกำลังพยายามใช้ SDL2

ฉันใช้ Visual Studio 2015 เป็นคอมไพเลอร์

ฉันได้เชื่อมโยงกับ SDL2.lib และ SDL2main.lib ใน Linker -> Input -> การอ้างอิงเพิ่มเติมและฉันได้ตรวจสอบให้แน่ใจว่า VC ++ Directories นั้นถูกต้อง


1
คุณช่วยพิสูจน์ได้ไหมโดยแสดงการตั้งค่าตัวเชื่อมโยงของคุณ
πάνταῥεῖ

@ πάνταῥεῖฉันได้เชื่อมโยงกับ SDL2.lib และ SDL2main.lib ในการตั้งค่าตัวเชื่อมต่ออินพุตและฉันได้ตรวจสอบให้แน่ใจว่าไดเร็กทอรีชี้ไปยังไดเร็กทอรีที่ถูกต้อง
RockFrenzy

1
อาจเกิดข้อผิดพลาด
dewaffled

คำตอบ:


123

ในที่สุดฉันก็ค้นพบสาเหตุที่เกิดขึ้น!

ในวิชวลสตูดิโอ 2015 stdin, stderr, stdout ถูกกำหนดไว้ดังนี้:

#define stdin  (__acrt_iob_func(0))
#define stdout (__acrt_iob_func(1))
#define stderr (__acrt_iob_func(2))

แต่ก่อนหน้านี้พวกเขาถูกกำหนดให้เป็น:

#define stdin  (&__iob_func()[0])
#define stdout (&__iob_func()[1])
#define stderr (&__iob_func()[2])

ตอนนี้ __iob_func จึงไม่ได้กำหนดไว้อีกต่อไปซึ่งนำไปสู่ข้อผิดพลาดในการเชื่อมโยงเมื่อใช้ไฟล์. lib ที่คอมไพล์ด้วย Visual Studio เวอร์ชันก่อนหน้า

ในการแก้ปัญหาคุณสามารถลองกำหนด__iob_func()ตัวเองว่าจะส่งคืนอาร์เรย์ที่มี{*stdin,*stdout,*stderr}.

เกี่ยวกับข้อผิดพลาดลิงก์อื่น ๆ เกี่ยวกับฟังก์ชัน stdio (ในกรณีของฉันคือsprintf()) คุณสามารถเพิ่มlegacy_stdio_definitions.libในตัวเลือกตัวเชื่อมโยงของคุณ


1
ขอบคุณสำหรับการติดตามสิ่งนี้ IIRC ปัญหาเกี่ยวกับ {* stdin, * stdout, * stderr} อาจเป็นไปได้ว่าหน่วยคอมไพล์ที่แตกต่างกันอาจมีสำเนา stdin 'ของตัวเอง' ซึ่งเป็นสาเหตุที่เรียกฟังก์ชันเหล่านี้โดยตรง
Steven R.Louomis

3
ที่แก้ไขให้ฉันเช่นกันเป็นเพียงการเตือนความจำเพื่อใช้extern "C"ในการประกาศ / คำจำกัดความ
Vargas

4
ใครสามารถเขียนว่าฟังก์ชันการแทนที่ควรมีลักษณะอย่างไร? ฉันลองใช้รูปแบบต่างๆและได้รับข้อผิดพลาดในการคอมไพล์ ขอบคุณ.
Milan Babuškov

55
extern "C" { FILE __iob_func[3] = { *stdin,*stdout,*stderr }; }
PoL0

1
คำจำกัดความ iob_func ด้านบนใช้ไม่ได้โปรดดูคำตอบของ MarkH สำหรับคำจำกัดความที่ถูกต้อง (คุณไม่สามารถกำหนดฟังก์ชันเป็นอาร์เรย์และคาดว่าการโทรจะทำงานได้)
Hans Olsson

59

สำหรับ Milan Babuškov, IMO นี่คือสิ่งที่ฟังก์ชันทดแทนควรมีลักษณะดังนี้ :-)

FILE _iob[] = {*stdin, *stdout, *stderr};

extern "C" FILE * __cdecl __iob_func(void)
{
    return _iob;
}

5
ขาด #ifdef สำหรับ MSVC และสำหรับเวอร์ชัน MSVC <2015
paulm

1
ดังที่ MarkH บันทึกไว้ในคำตอบอื่นที่ดูเหมือนถูกต้อง แต่จะไม่ได้ผล
Hans Olsson

4
@paulm #if defined(_MSC_VER) && (_MSC_VER >= 1900)ฉันคิดว่าคุณหมายถึง
Jesse Chisholm

@JesseChisholm อาจจะขึ้นอยู่กับว่าสิ่งนี้ใช้กับ MSVC ทุกรุ่นในอนาคตที่รู้จักกันหรือไม่;)
พอล

42

Microsoft มีหมายเหตุพิเศษเกี่ยวกับเรื่องนี้ ( https://msdn.microsoft.com/en-us/library/bb531344.aspx#BK_CRT ):

ขณะนี้ฟังก์ชันตระกูล printf และ scanf ถูกกำหนดแบบอินไลน์

คำจำกัดความของฟังก์ชัน printf และ scanf ทั้งหมดถูกย้ายแบบอินไลน์ไปที่stdio.h , conio.hและส่วนหัว CRT อื่น ๆ นี่คือการเปลี่ยนแปลงขั้นสุดท้ายที่นำไปสู่ข้อผิดพลาดของตัวเชื่อมต่อ (LNK2019 สัญลักษณ์ภายนอกที่ยังไม่ได้รับการแก้ไข) สำหรับโปรแกรมใด ๆ ที่ประกาศฟังก์ชันเหล่านี้ภายในเครื่องโดยไม่รวมส่วนหัว CRT ที่เหมาะสม หากเป็นไปได้คุณควรอัปเดตโค้ดเพื่อรวมส่วนหัว CRT (นั่นคือเพิ่ม #include) และฟังก์ชันแบบอินไลน์ แต่ถ้าคุณไม่ต้องการแก้ไขโค้ดของคุณเพื่อรวมไฟล์ส่วนหัวเหล่านี้ทางเลือกอื่นคือการเพิ่ม ห้องสมุดเพื่อป้อนตัวเชื่อมโยงของคุณlegacy_stdio_definitions.lib

ในการเพิ่มไลบรารีนี้ไปยังอินพุตตัวเชื่อมโยงของคุณใน IDE ให้เปิดเมนูบริบทสำหรับโหนดโปรเจ็กต์เลือกคุณสมบัติจากนั้นในกล่องโต้ตอบคุณสมบัติโครงการให้เลือกตัวเชื่อมโยงและแก้ไขอินพุต Linker เพื่อเพิ่ม legacy_stdio_definitions.lib ไปยังเซมิโคลอน - แยกรายการ

หากโปรเจ็กต์ของคุณเชื่อมโยงกับไลบรารีแบบสแตติกที่คอมไพล์ด้วย Visual C ++ รุ่นก่อนปี 2015 ตัวเชื่อมโยงอาจรายงานสัญลักษณ์ภายนอกที่ไม่ได้รับการแก้ไข ข้อผิดพลาดเหล่านี้อาจอ้างอิงข้อกำหนด stdio ภายในสำหรับ_iob , _iob_funcหรือการนำเข้าที่เกี่ยวข้องสำหรับฟังก์ชัน stdio บางฟังก์ชันในรูปแบบของ__imp_ *. Microsoft ขอแนะนำให้คุณคอมไพล์ไลบรารีแบบคงที่ทั้งหมดใหม่ด้วยเวอร์ชันล่าสุดของคอมไพเลอร์ Visual C ++ และไลบรารีเมื่อคุณอัปเกรดโปรเจ็กต์ ถ้าไลบรารีเป็นไลบรารีของบุคคลที่สามซึ่งไม่มีแหล่งที่มาคุณควรขอไบนารีที่อัปเดตจากบุคคลที่สามหรือสรุปการใช้งานไลบรารีนั้นลงใน DLL แยกต่างหากที่คุณคอมไพล์ด้วยคอมไพเลอร์ Visual C ++ เวอร์ชันเก่า และห้องสมุด


7
หรือ#pragma comment(lib, "legacy_stdio_definitions.lib")- แต่ไม่สามารถแก้ไขได้__imp___iob_func- มี lib แบบดั้งเดิมสำหรับสิ่งนี้ด้วยหรือไม่?
bytecode77

29

ดังที่ได้ตอบไว้ข้างต้นคำตอบที่ถูกต้องคือการรวบรวมทุกอย่างด้วย VS2015 แต่สำหรับความสนใจสิ่งต่อไปนี้คือการวิเคราะห์ปัญหาของฉัน

สัญลักษณ์นี้ไม่ได้ถูกกำหนดไว้ในไลบรารีแบบคงที่ที่ Microsoft จัดเตรียมไว้ให้โดยเป็นส่วนหนึ่งของ VS2015 ซึ่งค่อนข้างแปลกเนื่องจากมีสัญลักษณ์อื่น ๆ ทั้งหมด ในการค้นหาสาเหตุเราต้องดูที่การประกาศฟังก์ชันนั้นและที่สำคัญกว่านั้นคือวิธีการใช้งาน

นี่คือตัวอย่างจากส่วนหัว 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 คือโครงสร้างข้อมูลแบบทึบที่เก็บข้อมูลสถานะที่สำคัญ) ผู้ใช้ฟังก์ชั่นนี้คือสามมาโคร stdin, stdout และ stderr ซึ่งใช้สำหรับการเรียกสไตล์ fscanf, fprintf ต่างๆ

ตอนนี้เรามาดูกันว่า 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:

  1. ต้องมีอาร์เรย์ของโครงสร้าง FILE สามชุดซึ่งสามารถจัดทำดัชนีในลักษณะเดียวกันกับก่อนหน้านี้
  2. เค้าโครงโครงสร้างของ FILE ไม่สามารถเปลี่ยนแปลงได้

การเปลี่ยนแปลงใด ๆ ในข้อใดข้อหนึ่งข้างต้นจะหมายถึงโค้ดที่คอมไพล์ที่มีอยู่ซึ่งเชื่อมโยงซึ่งจะผิดพลาดอย่างร้ายแรงหากมีการเรียก API นั้น

มาดูกันว่า FILE ถูกกำหนดไว้อย่างไร

อันดับแรกนิยามไฟล์ VS2008:

struct _iobuf {
        char *_ptr;
        int   _cnt;
        char *_base;
        int   _flag;
        int   _file;
        int   _charbuf;
        int   _bufsiz;
        char *_tmpfname;
        };
typedef struct _iobuf FILE;

และตอนนี้นิยามไฟล์ VS2015:

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 ดังนั้นจึงถูกจัดวางเป็นบล็อกของโครงสร้างที่มีโมฆะ * สมมติว่าการจัดตำแหน่ง 32 บิตองค์ประกอบเหล่านี้จะห่างกัน 4 ไบต์ ดังนั้น _iob [0] จึงอยู่ที่ออฟเซ็ต 0, _iob [1] อยู่ที่ออฟเซ็ต 4 และ _iob [2] อยู่ที่ออฟเซ็ต 8 รหัสการเรียกจะคาดว่า FILE จะยาวกว่ามากโดยจัดให้อยู่ที่ 32 ไบต์ในระบบของฉัน มันจะใช้ที่อยู่ของอาร์เรย์ที่ส่งคืนและเพิ่ม 0 ไบต์เพื่อไปยังองค์ประกอบศูนย์ (อันนั้นใช้ได้) แต่สำหรับ _iob [1] จะอนุมานได้ว่าต้องเพิ่ม 32 ไบต์และสำหรับ _iob [2] มันจะอนุมาน จำเป็นต้องเพิ่ม 64 ไบต์ (เนื่องจากเป็นลักษณะที่ปรากฏในส่วนหัว VS2008) และแน่นอนรหัสถอดสำหรับ VS2008 แสดงให้เห็นถึงสิ่งนี้

ปัญหารองของโซลูชันข้างต้นคือการคัดลอกเนื้อหาของโครงสร้าง FILE (* stdin) ไม่ใช่ที่จับ FILE * ดังนั้นรหัส 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;
}

คำตอบที่ถูกต้องคือคำตอบนี้การแก้ไขที่ง่ายที่สุดคือการอัปเกรดโครงการเป็น VS2015 แล้วคอมไพล์
Akumaburn

2
ในกรณีของฉันฉันต้องอัปเกรดหลายโปรเจ็กต์ (โปรเจ็กต์ C ++ และ C #) จาก Visual Studio 2013 เพื่อใช้ Visual Studio 2015 Update 3 ฉันต้องการเก็บ VC100 (Visual Studio 2010 C ++ คอมไพเลอร์) เมื่อสร้างโปรเจ็กต์ C ++ แต่ฉันมีข้อผิดพลาดเดียวกัน ดังกล่าวข้างต้น. ฉันแก้ไข imp _fprintfโดยเพิ่มlegacy_stdio_definitions.libไปยังตัวเชื่อมโยง ฉันจะแก้ไข_imp____iob_func ได้อย่างไร
Mohamed BOUZIDI

ก่อนที่จะตอบคำถามก่อนหน้าของฉันเป็นเรื่องปกติหรือไม่ที่ข้อผิดพลาดเหล่านี้เกิดขึ้นเมื่อใช้ msbuild 14 และ IntelCompiler 2016 และ VC100 เพื่อรวบรวมโปรเจ็กต์ C ++
Mohamed BOUZIDI

1
ฉันจะทำอะไรได้บ้างสำหรับการคอมไพล์ x64
athos

28

ฉันมีปัญหาเดียวกันใน VS2015 ฉันได้แก้ไขโดยรวบรวมแหล่งที่มาของ SDL2 ใน VS2015

  1. ไปที่http://libsdl.org/download-2.0.phpและดาวน์โหลดซอร์สโค้ด SDL 2
  2. เปิด SDL_VS2013.sln ในVS2015 คุณจะถูกขอให้แปลงโครงการ ทำมัน.
  3. คอมไพล์โครงการ SDL2
  4. รวบรวมโครงการ SDL2main
  5. ใช้ไฟล์เอาต์พุตที่สร้างขึ้นใหม่ SDL2main.lib, SDL2.lib และ SDL2.dll ในโครงการ SDL 2 ของคุณใน VS2015

4
BTW การสร้าง SDL 2.0.3 ต้องติดตั้ง DirectX SDK ในเดือนมิถุนายน 2010
โจ

1
ทำงานให้ฉันขอบคุณ !! แต่ฉันต้องรวบรวมSDL2mainและคัดลอกเท่านั้นSDL2main.lib
kgwong

10

ฉันไม่รู้ว่าทำไม แต่:

#ifdef main
#undef main
#endif

หลังจากรวม แต่ก่อนหลักของคุณควรแก้ไขจากประสบการณ์ของฉัน


1
.... เอาล่ะ .... ก่อนที่จะลองสิ่งนี้ฉันพูดกับตัวเองด้วยเสียงว่าฉันสงสัยว่ามันจะได้ผล แต่มันก็ทำได้ทั้งหมด ..... คุณอธิบายได้ไหมว่าทำไมถึงได้ผล ... ?
Trevor Hart

1
@TrevorHart ฉันเชื่อว่ามันไม่ได้กำหนด SDL หลักที่ "ผิดพลาด" รวมถึงการอ้างอิงถึงบัฟเฟอร์ที่ "ไม่ได้กำหนด" และหากคุณใช้บัฟเฟอร์ของคุณก็จะทำงานได้ดีและดี
The XGood

2
นี่เป็นการแฮ็กการปฏิบัติที่ไม่ดีอย่างน่ากลัว แต่เป็น 3 บรรทัดและใช้งานได้และช่วยให้ฉันไม่ต้องเจาะช่องโหว่ในการสร้าง SDL ดังนั้น ... ทำได้ดีมาก
Cheezmeister

1
@Cheezmeister การฝึกฝนที่ไม่ดีและการแฮ็กมักจำเป็นสำหรับทุกสิ่ง โดยเฉพาะสิ่งที่ไม่ควรต้องการ
The XGood


7

การเชื่อมโยงหมายความว่าทำงานไม่ถูกต้อง การขุดลงใน stdio.h ของ VS2012 และ VS2015 สิ่งต่อไปนี้ใช้ได้ผลสำหรับฉัน อนิจจาคุณต้องตัดสินใจว่าควรใช้กับ {stdin, stdout, stderr} อันใดอันหนึ่งหรือไม่

extern "C" FILE* __cdecl __iob_func()
{
    struct _iobuf_VS2012 { // ...\Microsoft Visual Studio 11.0\VC\include\stdio.h #56
        char *_ptr;
        int   _cnt;
        char *_base;
        int   _flag;
        int   _file;
        int   _charbuf;
        int   _bufsiz;
        char *_tmpfname; };
    // VS2015 has only FILE = struct {void*}

    int const count = sizeof(_iobuf_VS2012) / sizeof(FILE);

    //// stdout
    //return (FILE*)(&(__acrt_iob_func(1)->_Placeholder) - count);

    // stderr
    return (FILE*)(&(__acrt_iob_func(2)->_Placeholder) - 2 * count);
}

7

คำแนะนำของฉันคืออย่า (พยายาม) ใช้ __iob_func

ขณะแก้ไขข้อผิดพลาดเหล่านี้:

libpngd.v110.lib(pngrutil.obj) : error LNK2001: unresolved external symbol ___iob_func curllib.v110.lib(mprintf.obj) : error LNK2001: unresolved external symbol ___iob_func

ฉันลองใช้คำตอบของคำตอบอื่น ๆ แต่ในท้ายที่สุดการส่งคืนFILE*C-array ไม่ตรงกับโครงสร้าง IOB ภายในของ Windows @Volker เป็นสิทธิที่จะไม่เคยทำงานมานานกว่าหนึ่งstdin, หรือstdoutstderr

หากไลบรารีใช้หนึ่งในสตรีมเหล่านั้นจริงไลบรารีจะหยุดทำงาน ตราบใดที่โปรแกรมของคุณไม่ก่อให้เกิด lib ที่จะใช้พวกเขาคุณจะไม่เคยรู้ ตัวอย่างเช่นpng_default_errorเขียนถึงstderrเมื่อ CRC ไม่ตรงกับข้อมูลเมตาของ PNG (โดยปกติไม่ใช่ปัญหาที่เกิดข้อผิดพลาด)

สรุป: ไม่สามารถผสม VS2012 (Platform Toolset v110 / v110_xp) และ VS2015 + ไลบรารีได้หากใช้ stdin, stdout และ / หรือ stderr

วิธีแก้ไข: รวบรวมไลบรารีของคุณใหม่ที่มี__iob_funcสัญลักษณ์ที่ยังไม่ได้แก้ไขกับ VS เวอร์ชันปัจจุบันของคุณและชุดเครื่องมือแพลตฟอร์มที่ตรงกัน



2

ฉันแก้ไขปัญหานี้ด้วยฟังก์ชันต่อไปนี้ ฉันใช้ Visual Studio 2019

FILE* __cdecl __iob_func(void)
{
    FILE _iob[] = { *stdin, *stdout, *stderr };
    return _iob;
}

เนื่องจากการเรียกฟังก์ชันที่กำหนดด้วยมาโครของ stdin จึงไม่สามารถใช้นิพจน์ "* stdin" ได้ แต่การเริ่มต้นอาร์เรย์ท้องถิ่นเป็นไปได้ ขอโทษฉันภาษาอังกฤษไม่ดี


1

สำหรับใครที่ยังหาคำตอบว่าเทคนิคข้างต้นไม่ได้ผล การลิงก์แบบคงที่เป็นวิธีการแก้ปัญหานี้ เปลี่ยนการตั้งค่าไลบรารีรันไทม์ของคุณดังต่อไปนี้

Project properties --> C/C++ --> Code generation --> Runtime Library --> Multi-threaded Debug (/MTd) instead of /MDd


นี่คือการอภิปรายเกี่ยวกับโซลูชันนี้: social.msdn.microsoft.com/Forums/vstudio/en-US/…
Sisir

0

ฉันจัดการเพื่อแก้ไขปัญหา

แหล่งที่มาของข้อผิดพลาดคือโค้ดบรรทัดนี้ซึ่งสามารถพบได้ในซอร์สโค้ด SDLmain

fprintf(stderr, "%s: %s\n", title, message);

สิ่งที่ฉันทำคือแก้ไขซอร์สโค้ดใน SDLmain ของบรรทัดนั้นด้วย:

fprintf("%s: %s\n", title, message);

จากนั้นฉันก็สร้าง SDLmain และคัดลอกและแทนที่ SDLmain.lib เก่าในไดเร็กทอรีไลบรารี SDL2 ของฉันด้วยการสร้างและแก้ไขใหม่

จากนั้นเมื่อฉันรันโปรแกรมด้วย SDL2 จะไม่มีข้อความแสดงข้อผิดพลาดเกิดขึ้นและโค้ดก็ทำงานได้อย่างราบรื่น

ฉันไม่รู้ว่ามันจะกัดฉันในภายหลังหรือเปล่า แต่สำหรับทุกอย่างก็เป็นไปได้ด้วยดี


การเปลี่ยนแปลงของคุณเป็นความผิดพลาดในตัวเองและจะไม่ได้รับการแก้ไขปัญหาที่อธิบายไว้ในคำถามของคุณ เป็นเพียงเรื่องบังเอิญที่คุณไม่ได้รับข้อผิดพลาดของตัวเชื่อมโยงอีกต่อไปซึ่งอาจเป็นผลมาจากการที่คุณสร้างห้องสมุดขึ้นมาใหม่
Ross Ridge

@RossRidge โอ้ใช่นั่นอาจจะเป็นเช่นนั้น อืม.
RockFrenzy

0

สิ่งนี้สามารถเกิดขึ้นได้เมื่อคุณเชื่อมโยงไปยัง msvcrt.dll แทน msvcr10.dll (หรือคล้ายกัน) ซึ่งเป็นแผนการที่ดี เนื่องจากจะช่วยให้คุณสามารถแจกจ่ายไลบรารีรันไทม์ของ Visual Studio ภายในชุดซอฟต์แวร์สุดท้ายของคุณได้

วิธีแก้ปัญหานั้นช่วยฉันได้ (ที่ Visual Studio 2008):

#if _MSC_VER >= 1400
#undef stdin
#undef stdout
#undef stderr
extern "C" _CRTIMP extern FILE _iob[];
#define stdin   _iob
#define stdout  (_iob+1)
#define stderr  (_iob+2)
#endif

ข้อมูลโค้ดนี้ไม่จำเป็นสำหรับ Visual Studio 6 และคอมไพเลอร์ ดังนั้น #ifdef.


0

เพื่อให้เกิดความสับสนมากขึ้นในเธรดที่สมบูรณ์นี้ฉันบังเอิญไปเจอกับภายนอกที่ยังไม่ได้รับการแก้ไขบน fprintf

main.obj : error LNK2019: unresolved external symbol __imp__fprintf referenced in function _GenerateInfoFile

แม้ว่าในกรณีของฉันมันจะอยู่ในบริบทที่ค่อนข้างแตกต่าง: ภายใต้ Visual Studio 2005 (Visual Studio 8.0) และข้อผิดพลาดเกิดขึ้นในโค้ดของฉันเอง (เช่นเดียวกับที่ฉันกำลังรวบรวม) ไม่ใช่บุคคลที่สาม

เกิดข้อผิดพลาดนี้ถูกเรียกโดยตัวเลือก / MD ในแฟล็กคอมไพเลอร์ของฉัน การเปลี่ยนไปใช้ / MT ช่วยขจัดปัญหา นี่เป็นเรื่องแปลกเพราะโดยปกติแล้วการเชื่อมโยงแบบคงที่ (MT) จะก่อให้เกิดปัญหามากกว่าแบบไดนามิก (MD) ... แต่ในกรณีที่ให้บริการอื่นฉันใส่ไว้ที่นั่น


0

ในกรณีของฉันข้อผิดพลาดนี้มาจากการทดลองของฉันเพื่อลบการอ้างอิงกับไลบรารีรันไทม์ที่ขึ้นกับ MSVC เวอร์ชัน DLL (msvcr10.dll หรือมากกว่านั้น) และ / หรือลบไลบรารีรันไทม์แบบคงที่ด้วยเพื่อลบไขมันส่วนเกินออกจากไฟล์ปฏิบัติการของฉัน

ดังนั้นฉันจึงใช้สวิตช์ตัวเชื่อมต่อ / NODEFAULTLIB "msvcrt-light.lib" ที่สร้างขึ้นเอง (google ใช้เมื่อคุณต้องการ) และmainCRTStartup()/ WinMainCRTStartup()รายการ

เป็น IMHO ตั้งแต่ Visual Studio 2015 ดังนั้นฉันจึงติดอยู่กับคอมไพเลอร์รุ่นเก่า

อย่างไรก็ตามการกำหนดสัญลักษณ์_NO_CRT_STDIO_INLINEจะขจัดความยุ่งยากทั้งหมดและแอปพลิเคชัน "Hello World" ที่เรียบง่ายมีขนาดเล็ก 3 KB อีกครั้งและไม่ได้ขึ้นอยู่กับ DLL ที่ผิดปกติ ผ่านการทดสอบใน Visual Studio 2017


-2

วางรหัสนี้ในไฟล์ต้นฉบับของคุณแล้วสร้างใหม่ ทำงานให้ฉัน!

#include stdio.h

ไฟล์ _iob [3];

FILE * __cdecl __iob_func (โมฆะ) {

_iob [0] = * stdin;

_iob [0] = * stdout;

_iob [0] = * stderr;

กลับ _iob;

}


คุณควรเพิ่มการจัดรูปแบบด้วย `` และคำอธิบายด้วย
Antonin GAVREL

แม้ว่ารหัสนี้อาจช่วยแก้ปัญหาได้รวมถึงคำอธิบายว่าทำไมจึงแก้ปัญหานี้ได้จะช่วยปรับปรุงคุณภาพของโพสต์ของคุณได้อย่างแท้จริงและอาจส่งผลให้มีการโหวตเพิ่มขึ้น จำไว้ว่าคุณกำลังตอบคำถามสำหรับผู้อ่านในอนาคตไม่ใช่แค่คนที่ถามตอนนี้ โปรดแก้ไขคำตอบของคุณเพื่อเพิ่มคำอธิบายและระบุข้อ จำกัด และสมมติฐานที่ใช้
Brian
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.