Gọi một hàm C từ mã C ++


90

Tôi có một hàm C mà tôi muốn gọi từ C ++. Tôi không thể sử dụng " extern "C" void foo()" loại phương pháp tiếp cận vì không thể biên dịch hàm C bằng g ++. Nhưng nó biên dịch tốt bằng cách sử dụng gcc. Bất kỳ ý tưởng làm thế nào để gọi hàm từ C ++?


1
Bạn có thể vui lòng viết một số mã và ví dụ g++thông báo lỗi
Matthieu Rouget

7
Nếu bạn biên dịch nó bằng trình biên dịch C ++, thì đó là C ++. Mã C không phải biên dịch bằng trình biên dịch C ++. Chúng là những ngôn ngữ khác nhau. Mã của bạn không hợp lệ C ++ và mã đó không biên dịch bằng trình biên dịch C ++.
xaxxon

3
@MatthieuRougetvoid valid_in_C_but_not_in_CPlusPlus(size_t size) { char variable_length_array[size]; }
tự kỷ

2
void f(void *pv) { int *pi = pv; *pi = 42; }
Cố

1
Điều này nên được bỏ ngỏ, đặc biệt là vì nó có câu trả lời tốt chỉ ra cách trình biên dịch C (chứ không phải C ++) có thể được sử dụng cho mã C.
Chris Stratton

Câu trả lời:


126

Biên dịch mã C như sau:

gcc -c -o somecode.o somecode.c

Sau đó, mã C ++ như thế này:

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

Sau đó, liên kết chúng với nhau, bằng trình liên kết C ++:

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

Bạn cũng phải cho trình biên dịch C ++ biết một tiêu đề C sẽ xuất hiện khi bạn bao gồm khai báo cho hàm C. Vì vậy, hãy othercode.cppbắt đầu với:

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

somecode.h nên chứa một cái gì đó như:

 #ifndef SOMECODE_H_
 #define SOMECODE_H_

 void foo();

 #endif


(Tôi đã sử dụng gcc trong ví dụ này, nhưng nguyên tắc giống nhau đối với bất kỳ trình biên dịch nào. Hãy xây dựng riêng biệt như C và C ++, sau đó liên kết nó với nhau.)


7
@Arne Điểm tốt. Một số người tìm kiếm một số C ++ trong C của họ bằng cách gói phần đầu extern "C"trong tiêu đề với #ifdef __cplusplus.
thư giãn

@Arne Xem câu trả lời của tôi bên dưới. thư giãn Như bạn có thể thấy, tôi là một trong số họ;)
gx_

1
Cảm ơn bạn rất nhiều ! Đó là rất hữu ích với tôi :)
Hesham Eraqi

Tôi đã nhận được lỗi sau: Lỗi: # 337: đặc tả liên kết không tương thích với "foo" trước đó (được khai báo ở dòng 1) Bây giờ biên dịch của nó đã ổn. Bất cứ ai có thể giải thích?
FaizanHussainRabbani

@FaizanRabbani, không phải không có nhiều chi tiết hơn.
GS Falken

61

Hãy để tôi thu thập các bit và mảnh từ các câu trả lời và nhận xét khác, để cung cấp cho bạn một ví dụ với mã C và C ++ được phân tách rõ ràng:

Phần C:

foo.h :

#ifndef FOO_H
#define FOO_H

void foo(void);

#endif 

foo.c

#include "foo.h"

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

Biên dịch cái này với gcc -c -o foo.o foo.c.

Phần C ++:

bar.cpp

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

void bar() {
  foo();
}

Biên dịch cái này với g++ -c -o bar.o bar.cpp

Và sau đó liên kết tất cả lại với nhau:

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

Cơ sở lý luận: Mã C phải là mã C thuần túy, không có #ifdefs vì "có thể một ngày nào đó tôi sẽ gọi nó từ một ngôn ngữ khác". Nếu một số lập trình viên C ++ gọi các hàm C của bạn, đó là vấn đề của họ làm thế nào để thực hiện điều đó, không phải của bạn. Và nếu bạn là lập trình viên C ++, thì tiêu đề C có thể không phải là của bạn và bạn không nên thay đổi nó, vì vậy việc xử lý các tên hàm không bị nhầm lẫn (tức là extern "C") thuộc về mã C ++ của bạn.

Tất nhiên, bạn có thể viết cho mình một tiêu đề C ++ tiện lợi mà không làm gì khác ngoài việc gói tiêu đề C vào một extern "C"khai báo.


7
Có vẻ hợp pháp. +1 cho cơ sở lý luận
gx_

Cuối cùng là một lời giải thích hoàn toàn rõ ràng về điều này. Cảm ơn rất nhiều!
Daniel Soutar

16

Tôi đồng ý với câu trả lời của Giáo sư Falken , nhưng sau bình luận của Arne Mertz, tôi muốn đưa ra một ví dụ hoàn chỉnh (phần quan trọng nhất là #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
    // ...
}

Sau đó, bạn làm theo hướng dẫn của GS Falken để biên dịch và liên kết.

Điều này hoạt động bởi vì khi biên dịch với gcc, macro __cpluspluskhông được xác định, vì vậy tiêu đề được somecode.hbao gồm trong somecode.csẽ giống như thế này sau khi xử lý trước:

void foo(void);

và khi biên dịch với g++, then __cplusplus được định nghĩa, và do đó, tiêu đề được đưa vào othercode.cppbây giờ giống như vậy:

extern "C" {

void foo(void);

}

4
thb, tôi không thích #ifdef __cplusplusmã trong C. Mã C là cấp thấp hơn và không cần phải bận tâm nếu một ngày nào đó nó có thể được gọi từ mã C ++. Imo #ifdefchỉ được sử dụng trong mã C ++ nếu bạn muốn cung cấp tiêu đề liên kết C cho thư viện được viết bằng C ++, chứ không phải ngược lại.
Arne Mertz

2
@ GSFalken tất nhiên, nhưng nó là một định nghĩa có nghĩa là có thể cung cấp khả năng tương thích "xuống" của mã C ++, không phải cho mã C.
Arne Mertz,

1

Câu trả lời này được lấy cảm hứng từ một trường hợp mà cơ sở lý luận của Arne là đúng. Một nhà cung cấp đã viết một thư viện đã từng hỗ trợ cả C và C ++; tuy nhiên, phiên bản mới nhất chỉ hỗ trợ C. Các chỉ thị tiền nghiệm còn lại trong mã gây hiểu nhầm:

#ifdef __cplusplus
extern "C" {
#endif

Điều này khiến tôi mất vài giờ cố gắng biên dịch bằng C ++. Đơn giản chỉ cần gọi C từ C ++ đã dễ dàng hơn nhiều.

Quy ước ifdef __cplusplus vi phạm nguyên tắc trách nhiệm duy nhất. Mã sử ​​dụng quy ước này đang cố gắng thực hiện hai việc cùng một lúc:

  • (1) thực thi một hàm trong C - và -
  • (2) thực hiện cùng một hàm trong C ++

Nó giống như cố gắng viết bằng cả tiếng Anh Mỹ và Anh cùng một lúc. Điều này không cần thiết khiến cờ lê #ifdef __thequeensenglish #elif __yankeeenglish cờ lê #else trở thành một công cụ vô dụng khiến mã khó đọc hơn #endif vào mã.

Đối với mã đơn giản và thư viện nhỏ, quy ước ifdef __cplusplus có thể hoạt động; tuy nhiên, đối với các thư viện phức tạp, tốt nhất là chọn ngôn ngữ này hoặc ngôn ngữ kia và gắn bó với nó. Việc hỗ trợ một trong các ngôn ngữ sẽ mất ít thời gian bảo trì hơn là cố gắng hỗ trợ cả hai.

Đây là bản ghi về những sửa đổi tôi đã thực hiện đối với mã của Arne để biên dịch nó trên 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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.