símbolo externo não resolvido __imp__fprintf e __imp____iob_func, SDL2

108

Alguém poderia explicar o que

__imp__fprintf

e

__imp____iob_func

meios externos não resolvidos?

Porque recebo estes erros quando tento compilar:

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

Já posso dizer que o problema não é linkar errado. Vinculei tudo corretamente, mas por algum motivo ele não compila.

Estou tentando usar SDL2.

Estou usando o Visual Studio 2015 como compilador.

Vinculei a SDL2.lib e SDL2main.lib em Linker -> Entrada -> Dependências Adicionais e me certifiquei de que os Diretórios VC ++ estão corretos.

RockFrenzy
fonte
1
Você poderia provar isso mostrando suas configurações de vinculador, por favor.
πάντα ῥεῖ
@ πάνταῥεῖ, criei um link para SDL2.lib e SDL2main.lib nas configurações do vinculador de entrada e verifiquei se os diretórios estão apontando para o diretório correto.
RockFrenzy
1
possível duplicata do erro LNK2001 __imp_fprintf Visual Studio 2015 RC
desparafinado

Respostas:

123

Finalmente descobri por que isso está acontecendo!

No visual studio 2015, stdin, stderr, stdout são definidos como segue:

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

Mas anteriormente, eles eram definidos como:

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

Portanto, agora __iob_func não está mais definido, o que leva a um erro de link ao usar um arquivo .lib compilado com versões anteriores do visual studio.

Para resolver o problema, você pode tentar definir __iob_func()você mesmo que deve retornar um array contendo {*stdin,*stdout,*stderr}.

Em relação aos outros erros de link sobre funções stdio (no meu caso, foi sprintf()), você pode adicionar legacy_stdio_definitions.lib às opções do linker.

Deus
fonte
1
Obrigado por rastrear isso. IIRC o problema com {* stdin, * stdout, * stderr} pode ser que diferentes unidades de compilação podem ter sua 'própria' cópia de stdin, razão pela qual essas funções foram chamadas diretamente.
Steven R. Loomis
3
isso também resolveu para mim, apenas um lembrete para usar extern "C"na declaração / definição.
Vargas
4
Alguém pode escrever exatamente como a função de substituição deve ser? Tentei diferentes variantes e continuo recebendo erros de compilação. Obrigado.
Milan Babuškov
55
extern "C" { FILE __iob_func[3] = { *stdin,*stdout,*stderr }; }
PoL0 de
1
A definição iob_func acima não funciona, consulte a resposta de MarkH para uma definição correta. (Você não pode simplesmente definir uma função como uma matriz e esperar que as chamadas funcionem.)
Hans Olsson
59

Para Milan Babuškov, IMO, é exatamente assim que a função de substituição deve ser :-)

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

extern "C" FILE * __cdecl __iob_func(void)
{
    return _iob;
}
smartsl
fonte
5
Falta apenas um #ifdef para MSVC e para MSVC versão <2015
paulm
1
Como MarkH observa em outra resposta que parece correta, mas não funcionará.
Hans Olsson
4
@paulm, acho que você quer dizer #if defined(_MSC_VER) && (_MSC_VER >= 1900).
Jesse Chisholm
@JesseChisholm talvez, depende se isso também se aplica a todas as versões futuras conhecidas do MSVC ou não;)
paulm
42

A Microsoft tem uma observação especial sobre isso ( https://msdn.microsoft.com/en-us/library/bb531344.aspx#BK_CRT ):

As famílias de funções printf e scanf agora são definidas em linha.

As definições de todas as funções printf e scanf foram movidas em linha para stdio.h , conio.h e outros cabeçalhos CRT. Esta é uma alteração significativa que leva a um erro de vinculador (LNK2019, símbolo externo não resolvido) para quaisquer programas que declararam essas funções localmente sem incluir os cabeçalhos CRT apropriados. Se possível, você deve atualizar o código para incluir os cabeçalhos CRT (isto é, adicionar #include) e as funções embutidas, mas se você não quiser modificar seu código para incluir esses arquivos de cabeçalho, uma solução alternativa é adicionar um biblioteca para sua entrada de vinculador, legacy_stdio_definitions.lib .

Para adicionar esta biblioteca à sua entrada do vinculador no IDE, abra o menu de contexto para o nó do projeto, escolha Propriedades e, na caixa de diálogo Propriedades do projeto, escolha Vinculador e edite a Entrada do vinculador para adicionar legacy_stdio_definitions.lib ao ponto e vírgula -lista separada.

Se o seu projeto se vincular a bibliotecas estáticas que foram compiladas com uma versão do Visual C ++ anterior a 2015, o vinculador pode relatar um símbolo externo não resolvido. Esses erros podem fazer referência a definições internas de stdio para _iob , _iob_func ou importações relacionadas para certas funções de stdio na forma de __imp_ *. A Microsoft recomenda que você recompile todas as bibliotecas estáticas com a versão mais recente do compilador Visual C ++ e bibliotecas ao atualizar um projeto. Se a biblioteca for uma biblioteca de terceiros para a qual a fonte não está disponível, você deve solicitar um binário atualizado do terceiro ou encapsular seu uso dessa biblioteca em uma DLL separada que você compila com a versão mais antiga do compilador Visual C ++ e bibliotecas.

Kingsb
fonte
7
Ou #pragma comment(lib, "legacy_stdio_definitions.lib")- mas isso não corrige o __imp___iob_func- existe uma biblioteca legada para isso também?
bytecode77
29

Conforme respondido acima, a resposta certa é compilar tudo com o VS2015, mas por interesse o seguinte é minha análise do problema.

Este símbolo não parece ser definido em nenhuma biblioteca estática fornecida pela Microsoft como parte do VS2015, o que é bastante peculiar, pois todos os outros são. Para descobrir por que, precisamos olhar a declaração dessa função e, mais importante, como ela é usada.

Aqui está um snippet dos cabeçalhos do Visual Studio 2008:

_CRTIMP FILE * __cdecl __iob_func(void);
#define stdin (&__iob_func()[0])
#define stdout (&__iob_func()[1])
#define stderr (&__iob_func()[2])

Portanto, podemos ver que o trabalho da função é retornar o início de um array de objetos FILE (não manipuladores, o "FILE *" é o identificador, FILE é a estrutura de dados opaca subjacente que armazena os goodies de estado importantes). Os usuários desta função são as três macros stdin, stdout e stderr que são usadas para várias chamadas de estilo fscanf e fprintf.

Agora vamos dar uma olhada em como o Visual Studio 2015 define as mesmas coisas:

_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))

Portanto, a abordagem mudou para a função de substituição para agora retornar o identificador de arquivo em vez do endereço da matriz de objetos de arquivo, e as macros mudaram para simplesmente chamar a função passando um número de identificação.

Então, por que eles / nós não podemos fornecer uma API compatível? Existem duas regras principais que a Microsoft não pode violar em termos de sua implementação original via __iob_func:

  1. Deve haver uma matriz de três estruturas FILE que podem ser indexadas da mesma maneira que antes.
  2. O layout estrutural de FILE não pode ser alterado.

Qualquer mudança em qualquer uma das opções acima significaria código compilado existente vinculado a ele que iria muito mal se a API fosse chamada.

Vamos dar uma olhada em como FILE foi / é definido.

Primeiro, a definição de FILE do VS2008:

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

E agora a definição do arquivo VS2015:

typedef struct _iobuf
{
    void* _Placeholder;
} FILE;

Portanto, este é o ponto crucial: a estrutura mudou de forma. O código compilado existente referindo-se a __iob_func se baseia no fato de que os dados retornados são uma matriz que pode ser indexada e que nessa matriz os elementos estão à mesma distância.

As possíveis soluções mencionadas nas respostas acima ao longo dessas linhas não funcionariam (se chamadas) por alguns motivos:

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

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

O array FILE _iob seria compilado com VS2015 e, portanto, seria apresentado como um bloco de estruturas contendo um vazio *. Assumindo o alinhamento de 32 bits, esses elementos estariam separados por 4 bytes. Portanto, _iob [0] está no deslocamento 0, _iob [1] está no deslocamento 4 e _iob [2] está no deslocamento 8. Em vez disso, o código de chamada esperará que FILE seja muito mais longo, alinhado a 32 bytes em meu sistema, e assim pegará o endereço do array retornado e adicionará 0 bytes para chegar ao elemento zero (aquele está certo), mas para _iob [1] ele irá deduzir que precisa adicionar 32 bytes e para _iob [2] irá deduzir que ele precisa adicionar 64 bytes (porque é assim que parecia nos cabeçalhos do VS2008). E de fato o código desmontado para VS2008 demonstra isso.

Um problema secundário com a solução acima é que ela copia o conteúdo da estrutura FILE (* stdin), não o identificador FILE *. Portanto, qualquer código do VS2008 estaria olhando para uma estrutura subjacente diferente do VS2015. Isso pode funcionar se a estrutura contiver apenas ponteiros, mas é um grande risco. Em qualquer caso, a primeira questão torna isso irrelevante.

O único hack que consegui imaginar é aquele em que __iob_func percorre a pilha de chamadas para descobrir qual identificador de arquivo real eles estão procurando (com base no deslocamento adicionado ao endereço retornado) e retorna um valor calculado de modo que dá a resposta certa. Isso é tão insano quanto parece, mas o protótipo para x86 apenas (não x64) está listado abaixo para sua diversão. Funcionou bem em meus experimentos, mas sua milhagem pode variar - não é recomendado para uso em produção!

#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;
}
MarkH
fonte
A resposta correta é esta, a correção mais fácil é como afirmado para atualizar o projeto para VS2015 e, em seguida, compilar.
Akumaburn
2
No meu caso, preciso atualizar muitos projetos (projetos C ++ e C #) do visual studio 2013 para usar a atualização 3. do Visual Studio 2015. Quero manter o VC100 (compilador C ++ do Visual studio 2010) ao construir projetos C ++, mas tenho os mesmos erros como acima. Fixei imp _fprintf adicionando legacy_stdio_definitions.lib para o vinculador. Como posso corrigir também _imp____iob_func ?
Mohamed BOUZIDI
Antes de responder à minha pergunta anterior, é normal que esses erros ocorram ao usar msbuild 14 e IntelCompiler 2016 e VC100 para compilar projetos C ++?
Mohamed BOUZIDI
1
o que posso fazer para uma compilação x64?
athos
28

Eu tive o mesmo problema no VS2015. Eu resolvi isso compilando as fontes SDL2 no VS2015.

  1. Acesse http://libsdl.org/download-2.0.php e baixe o código-fonte do SDL 2.
  2. Abra SDL_VS2013.sln no VS2015 . Você será solicitado a converter os projetos. Faça.
  3. Compile o projeto SDL2.
  4. Compile o projeto principal SDL2.
  5. Use os novos arquivos de saída gerados SDL2main.lib, SDL2.lib e SDL2.dll em seu projeto SDL 2 no VS2015.

fonte
4
BTW, construir SDL 2.0.3 requer o SDK do DirectX de junho de 2010 para ser instalado.
Joe
1
Funcionou para mim, obrigado !! Mas eu só precisei compilar SDL2maine copiarSDL2main.lib
kgwong
10

Não sei porque mas:

#ifdef main
#undef main
#endif

Após o inclui, mas antes do seu principal deve corrigi-lo por experiência própria.

The XGood
fonte
1
.... Ok .... então antes de tentar isso eu audivelmente disse a mim mesmo que eu de alguma forma duvido que isso vá funcionar, mas funcionou totalmente ..... você pode explicar por que isso funciona ...?
Trevor Hart,
1
@TrevorHart Eu acredito que ele undefine um SDL principal "defeituoso", incluindo referências aos buffers "indefinidos" e se o seu usar seus buffers, então funciona muito bem.
The XGood de
2
Isso é uma prática horrível de hacks, mas tem 3 linhas e funciona e me salvou de ter que abrir caminho para construir o SDL, então ... muito bem feito.
Cheezmeister
1
@Cheezmeister Práticas inadequadas e hacks geralmente são necessários para tudo. Especialmente as coisas que não deveriam precisar deles.
The XGood
7

Ligar significa não funcionar corretamente. Investigando stdio.h de VS2012 e VS2015 o seguinte funcionou para mim. Infelizmente, você tem que decidir se deve funcionar para um de {stdin, stdout, stderr}, nunca mais de um.

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);
}
Volker
fonte
7

Meu conselho é não (tentar) implementar __iob_func.

Ao corrigir esses erros:

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

Eu tentei as soluções de outras respostas, mas no final, retornar um FILE*C-array não corresponde a um array de estruturas IOB internas do Windows. @Volker é certo que ele nunca vai trabalhar para mais de um stdin, stdoutou stderr.

Se uma biblioteca realmente USAR um desses fluxos, ele irá travar . Contanto que seu programa não faça com que a lib os use, você nunca saberá . Por exemplo, png_default_errorgrava stderrquando o CRC não corresponde aos metadados do PNG. (Normalmente não é um problema que vale a pena travar)

Conclusão: Não é possível misturar as bibliotecas VS2012 (Platform Toolset v110 / v110_xp) e VS2015 +, se usarem stdin, stdout e / ou stderr.

Solução: Recompile suas bibliotecas que têm __iob_funcsímbolos não resolvidos com sua versão atual do VS e um conjunto de ferramentas de plataforma correspondente.

Luc Bloom
fonte
2

Eu resolvo esse problema com a seguinte função. Eu uso o Visual Studio 2019.

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

como a chamada de função definida pela macro stdin, a expressão "* stdin" não pode ser usada no inicializador de matriz global. Mas o initialier de array local é possível. desculpe, eu sou pobre em inglês.

soen.kr
fonte
1

Para quem ainda está procurando uma resposta onde os truques acima não funcionaram. A ligação estática é a forma de resolver este problema. Altere as configurações da sua biblioteca Runtime conforme abaixo

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

Sisir
fonte
Aqui está uma discussão sobre esta solução: social.msdn.microsoft.com/Forums/vstudio/en-US/…
Sisir
0

Consegui consertar o problema.

A origem do erro foi esta linha de código, que pode ser encontrada no código-fonte SDLmain.

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

Então, o que fiz foi editar o código-fonte em SDLmain dessa linha também:

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

E então eu construí o SDLmain e copiei e substituí o antigo SDLmain.lib em meu diretório de biblioteca SDL2 pelo recém-criado e editado.

Então, quando executei meu programa com SDL2, nenhuma mensagem de erro apareceu e o código foi executado sem problemas.

Não sei se isso vai me morder mais tarde, mas então para tudo está indo muito bem.

RockFrenzy
fonte
Sua alteração é um erro por si só e não teria corrigido o problema descrito em sua pergunta. É apenas uma coincidência que você não esteja mais recebendo erros do vinculador, o que provavelmente é apenas o resultado de como você reconstruiu a biblioteca.
Ross Ridge
@RossRidge, ah sim, pode ter sido só isso. Ah bem.
RockFrenzy
0

Isso pode acontecer quando você vincula a msvcrt.dll em vez de msvcr10.dll (ou semelhante), o que é um bom plano. Porque isso o deixará livre para redistribuir a biblioteca de tempo de execução do Visual Studio dentro do pacote de software final.

Essa solução alternativa me ajuda (no 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

Este snippet não é necessário para o Visual Studio 6 e seu compilador. Portanto, o #ifdef.

user2699548
fonte
0

Para trazer mais confusão neste tópico já rico, aconteceu de encontrar o mesmo externo não resolvido em fprintf

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

Mesmo que no meu caso fosse em um contexto bem diferente: no Visual Studio 2005 (Visual Studio 8.0) e o erro estava acontecendo no meu próprio código (o mesmo que eu estava compilando), não em um terceiro.

Acontece que esse erro foi disparado pela opção / MD em meus sinalizadores de compilador. Mudar para / MT removeu o problema. Isso é estranho porque normalmente, vincular estaticamente (MT) levanta mais problema do que dinamicamente (MD) ... mas caso sirva outro, eu coloco lá.

Zzircônio
fonte
0

No meu caso, este erro vem de minha tentativa de remover dependências da biblioteca de runtime dependente da versão MSVC DLL (msvcr10.dll ou assim) e / ou remover a biblioteca de runtime estática também, para remover o excesso de gordura de meus executáveis.

Portanto, eu uso a opção de vinculador / NODEFAULTLIB, meu "msvcrt-light.lib" feito por mim mesmo (procure no google quando precisar) e mainCRTStartup()/ WinMainCRTStartup()entradas.

É IMHO desde o Visual Studio 2015, então me concentrei em compiladores mais antigos.

No entanto, definir o símbolo _NO_CRT_STDIO_INLINE remove todo o incômodo e um aplicativo simples "Hello World" tem novamente 3 KB de tamanho pequeno e não depende de DLLs incomuns. Testado no Visual Studio 2017.

Henrik Haftmann
fonte
-2

Cole este código em qualquer um dos seus arquivos de origem e recrie. Funcionou para mim!

#include stdio.h

ARQUIVO _iob [3];

ARQUIVO * __cdecl __iob_func (vazio) {

_iob [0] = * stdin;

_iob [0] = * saída padrão;

_iob [0] = * stderr;

return _iob;

}

Deep Patel
fonte
você deve adicionar formatação com `` `e também algumas explicações
Antonin GAVREL
Embora este código possa resolver a questão, incluir uma explicação de como e por que isso resolve o problema realmente ajudaria a melhorar a qualidade de sua postagem e provavelmente resultaria em mais votos positivos. Lembre-se de que você está respondendo à pergunta para os leitores no futuro, não apenas para a pessoa que está perguntando agora. Por favor edite sua resposta para adicionar explicações e dar uma indicação do que limitações e premissas se aplicam.
Brian,