Llamar a una función C desde código C ++

90

Tengo una función en C que me gustaría llamar desde C ++. " extern "C" void foo()" No pude usar el tipo de enfoque porque la función C no pudo compilarse usando g ++. Pero se compila bien usando gcc. ¿Alguna idea de cómo llamar a la función desde C ++?

Dangila
fuente
1
¿Podría escribir algún código de ejemplo y g++mensajes de error?
Matthieu Rouget
7
Si lo compila con un compilador de C ++, es C ++. El código C no tiene que compilarse con un compilador C ++. Son idiomas diferentes. Su código no es válido para C ++ y por lo tanto no se compila con un compilador de C ++.
xaxxon
3
@MatthieuRougetvoid valid_in_C_but_not_in_CPlusPlus(size_t size) { char variable_length_array[size]; }
autista
2
Mi intento: void f(void *pv) { int *pi = pv; *pi = 42; }^^
gx_
1
Esto debe dejarse abierto, especialmente porque tiene buenas respuestas que señalan cómo el compilador C (en lugar de C ++) se puede usar para el código C.
Chris Stratton

Respuestas:

126

Compile el código C así:

gcc -c -o somecode.o somecode.c

Entonces el código C ++ como este:

g++ -c -o othercode.o othercode.cpp

Luego, vincúlelos con el vinculador de C ++:

g++ -o yourprogram somecode.o othercode.o

También tiene que decirle al compilador de C ++ que viene un encabezado de C cuando incluye la declaración para la función de C. Entonces othercode.cppcomienza con:

extern "C" {
#include "somecode.h"
}

somecode.h debe contener algo como:

 #ifndef SOMECODE_H_
 #define SOMECODE_H_

 void foo();

 #endif


(Usé gcc en este ejemplo, pero el principio es el mismo para cualquier compilador. Compile por separado como C y C ++, respectivamente, luego vincúlelo).

Prof. Falken
fuente
7
@Arne Buenos puntos. Algunas personas introducen algo de C ++ en su C envolviendo extern "C"el encabezado con #ifdef __cplusplus.
relajarse
@Arne Vea mi respuesta a continuación. relajarse Como puede ver, soy uno de ellos;)
gx_
1
Muchas gracias ! Eso fue muy útil para mí :)
Hesham Eraqi
Recibí el siguiente error: Error: # 337: la especificación de enlace es incompatible con el "foo" anterior (declarado en la línea 1). Ahora la compilación está bien. ¿Alguien puede explicarlo?
FaizanHussainRabbani
@FaizanRabbani, no sin muchos más detalles.
Prof. Falken
61

Permítame recopilar los fragmentos de las otras respuestas y comentarios, para darle un ejemplo con código C y C ++ claramente separado:

La parte C:

foo.h :

#ifndef FOO_H
#define FOO_H

void foo(void);

#endif 

foo.c

#include "foo.h"

void foo(void)
{
    /* ... */
}

Compile esto con gcc -c -o foo.o foo.c.

La parte de C ++:

bar.cpp

extern "C" {
  #include "foo.h" //a C header, so wrap it in extern "C" 
}

void bar() {
  foo();
}

Compila esto con g++ -c -o bar.o bar.cpp

Y luego vincularlo todo junto:

g++ -o myfoobar foo.o bar.o

Justificación: El código C debe ser código C simple, no #ifdefs para "quizás algún día llamaré a esto desde otro idioma". Si algún programador de C ++ llama a sus funciones de C, es su problema cómo hacerlo, no el suyo. Y si usted es el programador de C ++, entonces el encabezado de C podría no ser suyo y no debería cambiarlo, por lo que el manejo de los nombres de funciones sin alterar (es decir, el extern "C") pertenece a su código de C ++.

Por supuesto, puede escribir usted mismo un encabezado C ++ conveniente que no haga nada más que envolver el encabezado C en una extern "C"declaración.

Arne Mertz
fuente
7
Parece legitimo. +1 para la justificación
gx_
Finalmente una explicación completamente clara de esto. ¡Gracias una tonelada!
Daniel Soutar
16

Estoy de acuerdo con la respuesta del profesor Falken , pero después del comentario de Arne Mertz quiero dar un ejemplo completo (la parte más importante es la #ifdef __cplusplus):

somecode.h

#ifndef H_SOMECODE
#define H_SOMECODE

#ifdef __cplusplus
extern "C" {
#endif

void foo(void);

#ifdef __cplusplus
}
#endif

#endif /* H_SOMECODE */

somecode.c

#include "somecode.h"

void foo(void)
{
    /* ... */
}

othercode.hpp

#ifndef HPP_OTHERCODE
#define HPP_OTHERCODE

void bar();

#endif /* HPP_OTHERCODE */

othercode.cpp

#include "othercode.hpp"
#include "somecode.h"

void bar()
{
    foo(); // call C function
    // ...
}

Luego siga las instrucciones del Prof. Falken para compilar y vincular.

Esto funciona porque al compilar con gcc, la macro __cplusplusno está definida, por lo que el encabezado somecode.hincluido en somecode.ces así después del preprocesamiento:

void foo(void);

y cuando se compila con g++, entonces __cplusplus se define, por lo que el encabezado incluido en othercode.cppahora es así:

extern "C" {

void foo(void);

}
gx_
fuente
4
thb, no me gusta el #ifdef __cpluspluscódigo en C. El código C es el nivel más bajo, y no debería tener que preocuparse si algún día podría ser llamado desde el código C ++. Imo que #ifdeftiene su uso solo en código C ++ si desea proporcionar un encabezado de enlace C para una biblioteca escrita en C ++, y no al revés.
Arne Mertz
2
@ Prof. Falken, por supuesto, pero es una definición destinada a poder proporcionar compatibilidad "descendente" del código C ++, no para el código C.
Arne Mertz
1

Esta respuesta está inspirada en un caso en el que el razonamiento de Arne era correcto. Un proveedor escribió una biblioteca que una vez admitió tanto C como C ++; sin embargo, la última versión solo admite C. Las siguientes directivas vestigiales que quedan en el código eran engañosas:

#ifdef __cplusplus
extern "C" {
#endif

Esto me costó varias horas intentar compilar en C ++. Simplemente llamar a C desde C ++ fue mucho más fácil.

La convención ifdef __cplusplus viola el principio de responsabilidad única. Un código que utiliza esta convención intenta hacer dos cosas a la vez:

  • (1) ejecutar una función en C - y -
  • (2) ejecutar la misma función en C ++

Es como intentar escribir en inglés americano y británico al mismo tiempo. Esto es innecesariamente lanzar un #ifdef __thequeensenglish spanner #elif __yankeeenglish wrench #else una herramienta inútil que hace que el código sea más difícil de leer #endif en el código.

Para código simple y bibliotecas pequeñas, la convención ifdef __cplusplus puede funcionar; sin embargo, para bibliotecas complejas es mejor elegir un idioma u otro y seguir con él. Admitir uno de los idiomas requerirá menos mantenimiento que intentar admitir ambos.

Este es un registro de las modificaciones que hice al código de Arne para que se compilara en Ubuntu Linux.

foo.h :

#ifndef FOO_H
#define FOO_H

void foo(void);

#endif 

foo.c

#include "foo.h"
#include <stdio.h>

void foo(void)
{
     // modified to verify the code was called
     printf("This Hello World was called in C++ and written in C\n");
}

bar.cpp

extern "C" {
    #include "foo.h" //a C header, so wrap it in extern "C" 
}

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

Makefile

# -*- MakeFile -*-
# dont forget to use tabs, not spaces for indents
# to use simple copy this file in the same directory and type 'make'

myfoobar: bar.o foo.o
    g++ -o myfoobar foo.o bar.o 

bar.o: bar.cpp
    g++ -c -o bar.o bar.cpp

foo.o: foo.c
    gcc -c -o foo.o foo.c
Agricultor
fuente