quinta-feira, 11 de agosto de 2011

extern "C"


Como muitos já sabem, para se chamar uma função de um código em C por outro código em C++, é necessário colocar um extern “C” antes de sua declaração.
Por exemplo, suponha que você tem os seguintes arquivos:

//hello_world.h
#if !defined (HELLO_WORLD_H_)
#define (HELLO_WORLD_H_)

void imprime();

#endif


//hello_world.c
#include <stdio.h>
#include “hello_world.h”

void imprime()
{
    printf(“Hello world!”);
}


//main.cpp
#include “hello_world.h”

int main()
{
    imprime();
    return 0;
}

Se tentar compilar assim, vai dar um erro do tipo “unresolved external symbol” ou coisa do tipo.

Para resolver o problema, é necessário alterar ou o hello_world.h para:

//hello_world.h
#if !defined (HELLO_WORLD_H_)
#define (HELLO_WORLD_H_)

#if defined (__cplusplus) //já explico o que é isso
extern “C”
{
#endif

void imprime();

#if defined (__cplusplus)
}
#endif

#endif

ou o main.cpp para


//main.cpp
extern “C”
{
#include “hello_world.h”
}

int main()
{
    imprime();
    return 0;
}

O que muita gente não entende é por que isso é necessário.

Bom, vamos lá.
Quando você compila um código em C++, o compilador substitui o nome de cada função do seu código por um nome único. Isso é necessário porque em C++ você pode ter mais de uma função com o mesmo nome. Isso é chamado de name mangling (não sei se existe alguma tradução aceita para isso).
Por exemplo, suponha o seguinte código:

void Funcao( char* p ){...};
void Funcao( char* p, int a ){...};

int main()
{
    Funcao(“teste”);
    Funcao(“teste”, 5 );
    return 0;
}

Seu compilador transformará a declaração das funções em:

void __xxxy( char* p ){...};
void __txyz( char* p, int a ){...};

E as chamadas em:
__xxxy(“teste”);
__txyz(“teste”, 5 );

Note que em C isso não é necessário, pois em C não é permitido que duas funções possuam o mesmo nome.
Neste caso, no exemplo acima, o compilador de C manterá o nome “imprime” da função quando estiver compilando o “hello_world.c”, porém o compilador de C++ irá substituí-lo por alguma coisa do tipo “_xxacd” quando estiver compilando o main.cpp. Assim, quando o linker estiver linkando os objetos criados, não encontrará uma definição de “_xxacd” dentro do objeto compilado em C, e dará o erro citado.
O comando extern “C” diz para seu compilador de C++ que aquelas funções não devem sofrer o processo de mangling.

Agora, se você não entendeu o motivo do #if defined (__cplusplus) no exemplo acima, eu explico:
A macro __cplusplus é definida pelos compiladores de C++, porém não existe nos compiladores de C. Por isso, quando fizemos:

#if defined (__cplusplus)
extern “C”
{
#endif

O compilador de C que está compilando o hello_world.c irá ignorar o extern “C”, pois a macro __cplusplus não está definida.
Se não colocássemos isso, o compilador de C acusaria um erro no código, pois ele não conhece o comando extern “C”.

Um comentário: