同一程序中混合调用C和C++代码

转自:http://blog.csdn.net/yzd_real/article/details/2335162


// 觉得这篇文章写的还可以比较详细有点学究的味道所以就翻译过来。C++C混合编码虽然不难理解,但C库、C++库、extern "C"extern "C++"#inlcude <stdio.h>#include <CStdio>等等,区别起来也有点困难。发生误解的根源在于没有把编译和连接理解透彻。一个程序使用了某个函数,不管该函数是在某个头文件中定义的函数,还是通过extern定义的外部函数,还是本地已经定义好的函数,该函数都要经过编译、连接两个步骤。在编译阶段,C++编译器会根据函数返回类型、参数类型等,进行函数名修饰;之后才会根据修饰后的函数名,进行连接。(注意函数名修饰发生在编译阶段)因此,在定义可同时被CC++使用的头文件时,要考虑到CC++编译器的编译过程,综合使用extern "C"、#ifdef __cplusplus(所有C++编译器都会预定义这个头文件)来声明该头文件。

// 本文中:源代码(Source),程序(Program)是指未编译的程序;代码(Code)应该指的是头文件(.H)加库(.LIB / .DLL)的组合。
C++ 语言提供了这种机制:它允许在同一个程序中有 C 编译器 C++ 编译器 编译的代码(程序库)混合存在。本文主要解决由于 C C++ 代码混合使用所引起的一些通用问题,同时注明了几个容易引起的误区。
主要内容
-使用可兼容的编译器
C++ 源程序中调用 C 代码
C 源程序中调用 C++ 代码
-混合 IOstream C 标准 I/O        
-函数指针的处理
C++ 异常的处理
-程序的连接
 
1. 使用可兼容的编译器
 
本文的讨论建立在这样的基础上:所使用 C C++ 编译器是兼容的;它们都以同种方式定义 int float pointer 等数据类型。
C 编译器所使用的 C 运行时库也要和 C++ 编译器兼容。 C++ 包含了 C 运行时库,视为它的一个子集。如果 C++ 编译器提供它自己的 C 版本头文件,这些头文件也要和 C 编译器兼容。
 
2. C++源程序中调用C代码
 
C++ 语言为了支持重载,提供了一种连接时的 函数名修饰 。对 C++ 文件( .CPP )文件的编译、连接,缺省采用的是这种 C++ 的方式 ,但是所有 C++ 编译器都支持 C 连接 (无函数名修饰)。
当需要调用 C 连接 (由 C 编译器编译得到的)时,即便几乎所有 C++ 编译器对 数据 连接修饰 C 编译器无任何差异,但还是应该在 C++代码中声明C连接 ;指向函数的指针没有 C 连接 C++ 连接
 
能够对 连接修饰 进行嵌套,如下,这样不会创建一个 scope ,所有函数都处于同一个全局 scope
 
extern "C" {
    void f();                // C linkage
    extern "C++" {
        void g();            // C++ linkage
        extern "C" void h(); // C linkage
        void g2();           // C++ linkage
    }
    extern "C++" void k();   // C++ linkage
    void m();                // C linkage
}
 
如果使用 C 库及其对应的 .H 头文件 往往可以这样做
   
extern "C" {
    #include "header.h";
}
 
建立支持多语言的.H头文件如同时支持CC++的头文件时需要把所有的声明放在extern "C"的大括号里头但是C编译器却不支持 " extern "C" "这种语法。每一个C++编译器都会预定义__cplusplus宏,可以用这个宏确保C++的语法扩展。
   
#ifdef __cplusplus
extern "C" {
#endif
    /* body of header */
#ifdef __cplusplus
}
#endif
 
假如想在 C++ 代码中更加方便的使用 C 库,例如在 C++ 类的成员函数 / 虚函数中使用 "C " ,怎样确保 "C " 中的函数能够正确识别出 "C++" 的类?利用 extern "C" 可以这样做:
 
struct buf {
    char* data;
    unsigned count;
};
void buf_clear(struct buf*);
int buf_print(struct buf*);
int buf_append(struct buf*, const char*, unsigned count);
 
C++ 中可以方便的使用这个结构,如下:
 
extern "C" {
    #include "buf.h";
}
class mybuf {
public:
    mybuf() : data(0), count(0) {}
    void clear() { buf_clear((buf*)this); }
    bool print() { return buf_print((buf*)this); }
    bool append()...
private:
    char* data;
    unsigned count;                
} ;
 
提供给 class mybuf 的接口看起来更像 C++ Code 它能够更加容易的被集成到面向对象编程中。但是,这个例子是在没有虚函数、且类的数据区开头没有冗余数据的情况下。
 
另一个可供替代的方案是,保持 struct buf 的独立性,而从其派生出 C++ 的类。当传递指针到 struct buf 的成员函数时,即使指向 mybuf 的指针数据与 struct buf 位置不完全吻合, C++ 编译器也会自动调整,把类的类型协变到 struct buf class mybuf layout 可能会随不同的 C++ 编译器而不同,但是这段操作 mybuf buf C++ 源代码也能到哪里都工作。如下是这种派生的源代码,它也隐含了 struct 结构具有的面向对象的特性:
 
extern "C" {
 #include "buf.h"
}
class mybuf : public buf { // a portable solution
public:
    mybuf() : data(0), count(0) { }
    void clear() { buf_clear(this); }
    bool print() { return buf_print(this); }
    bool append(const char* p, unsigned c)
        { return buf_append(this, p, c); }
};
 
C++ 代码能够自由地使用 mybuf 类,传递自身到 struct buf C 代码中,能很好的工作,当然,如果给 mybuf 加入了别的成员变量, C 代码是不知道的。这是 派生类 的一种常规设计思路。
 
3. C源代码中调用C++代码
 
如果声明 C++ 函数采用 C 连接 ,那么它就能够被 "C 代码 " 引用,前提是这个函数的参数和返回值必须能够被 "C 代码 " 所接受。如果该函数接受一个 IOStream 的类作为参数,那么 C 将不能使用,因为 C 编译器没有没有 C++ 的这个模板库。下面是一个 C++ 函数采用 C 连接 的例子:
 
#include <iostream>
extern "C" int print(int i, double d)
{
    std::cout << "i = " << i << ", d = " << d;
}
 
可以这样定义一个能同时被 C C++ 使用的头文件:
 
#ifdef __cplusplus
extern "C"
#endif
int print(int i, double d);
 
对于 C++ 同名重载函数,利用 extern "C" 声明时,最多只能声明 重载函数系列 中的一个函数。如果想引用所有重载的函数,就需要对 C++ 重载的函数外包一个 Wrapper 。代码实例如下:
 
int    g(int);
double g(double);
extern "C" int    g_int(int i)       { return g(i); }
extern "C" double g_double(double d) { return g(d); }
 
wrapper 的头文件可以这样写 :
 
int g_int(int);
double g_double(double);
 
模板函数不能用 extern "C" 修饰 也可以采取 wrapper 的方式 如下
 
template<class T> T foo(T t) { ... }
extern "C" int   foo_of_int(int t) { return foo(t); }
extern "C" char* foo_of_charp(char* p) { return foo(p); }
 
4. C代码中访问C++的类
 
能否声明一个类似与 C++ 类的 Struct ,从而调用其成员函数,达到 C 代码访问 C++ 类的目的呢?答案是可以的,但是,为了保持可移植性,必须要加入一个兼容的措施。修改 C++ 类时,也要考虑到调用它的 C 代码。加入有一个 C++ 类如下:
 
class M {
public:
    virtual int foo(int);
    // ...
private:
    int i, j;
};
 
C代码中无法声明Class M 最好的方式是采用指针。 C++ 代码中声明如下
 
extern "C" int call_M_foo(M* m, int i) { return m->foo(i); }
 
C 代码中,可以这样调用:
 
struct M;                        /* you can supply only an incomplete declaration */
int call_M_foo(struct M*, int);     /* declare the wrapper function */
int f(struct M* p, int j)             /* now you can call M::foo */
    { return call_M_foo(p, j); }
 
5. 混合IOstreamC标准I/O
 
C++ 程序中 可以通过 C 标准头文件 <stdio.h> 使用 C 标准 I/O 因为 C 标准 I/O C++ 的一部分。程序中混合使用 IOstream 和标准 I/O 与程序是否含有 C 代码没有必然联系。
C++ 标准说可以在同一个目标 stream 上混合 C 标准 I/O IOstream 流,例如标注输入流、标准输出流,这一点不同的 C++ 编译器实现却不尽相同,有的系统要求用户在进行 I/O 操作前显式地调用 sync_with_stdio() 。其它还有程序调用性能方面的考虑。
 
6. 如何使用函数指针
 
必须确定一个函数指针究竟是指向 C 函数还是 C++ 函数。因为 C C++ 函数采用不同的调用约定。如果不明确指针究竟是 C 函数还是 C++ 函数,编译器就不知道应该生成哪种调用代码。如下
 
typedef int (*pfun)(int);      // line 1
extern "C" void foo(pfun); // line 2
extern "C" int g(int)            // line 3
...
foo( g ); // Error!        // line 5
 
第一行声明一个 C++ 函数指针 因为没有 link specifier );
第二行声明 foo 是一个 C 函数 但是它接受一个 C++ 函数指针
第三行声明 g 是一个 C 函数;
第五行出现类型不匹配;
 
解决这个问题可以如下:
 
extern "C" {
    typedef int (*pfun)(int);
    void foo(pfun);
    int g(int);
}
foo( g ); // now OK
 
当把 linkage specification 作用于函数参数或返回值类型时 函数指针还有一个难以掌握的误区。当在函数参数声明中嵌入一个函数指针的声明时,作用于函数声明的 linkage specification 也会作用到这个函数指针的声明中。如果用 typedef 声明的函数指针,那么这个声明可能会失去效果,如下:
 
typedef int (*pfn)(int);
extern "C" void foo(pfn p) { ... }     // definition
extern "C" void foo( int (*)(int) );   // declaration
 
假定前两行出现在源程序中。
第三行出现在头文件中,因为不想输出一个私有定义的 typedef 。尽管这样做的目的是为了使函数声明和定义吻合,但结果却是相反的。 foo 的定义是接受一个 C++ 的函数的指针,而 foo 的声明却是接受一个 C 函数指针,这样就构成两个同名函数的重载。为了避免这种情况,应该使 typedef 紧靠函数声明。例如,如果想声明 foo 接受一个 C 函数指针,可以这样定义:
 
extern "C" {
    typedef int (*pfn)(int);
    void foo(pfn p) { ... }
};
 
7. 处理C++异常
 
C函数调用C++函数时,如果C++函数抛出异常,应该怎样解决呢?可以在C程序使用用long_jmp处理,只要确信long_jmp的跳转范围,或者直接把C++函数编译成不抛出异常的形式。
 
8. 程序的连接
 
过去大部分C++编译器要求把main()编译到程序中,目前这个需求已经不太普遍。如果还需要,可以通过更改C程序的main函数名,在C++通过wrapper的方式调用。例如,把C程序的main函数改为
C_main,这样写C++程序:
 
extern "C" int C_main(int, char**); // not needed for Sun C++
int main(int argc, char** argv)
{
    return C_main(argc, argv);
}
 
当然,C_main必须在C程序中被声明为返回值为int型的函数。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值