现在的位置: 首页 > 综合 > 正文

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

2013年01月07日 ⁄ 综合 ⁄ 共 5605字 ⁄ 字号 评论关闭

转自: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++编译器编译的代码(程序库)混合存在。本文主要解决由于CC++代码混合使用所引起的一些通用问题,同时注明了几个容易引起的误区。
主要内容
-使用可兼容的编译器
C++源程序中调用C代码
C源程序中调用C++代码
-混合IOstreamC标准I/O        
-函数指针的处理
C++异常的处理
-程序的连接
 
1. 使用可兼容的编译器
 
本文的讨论建立在这样的基础上:所使用CC++编译器是兼容的;它们都以同种方式定义intfloatpointer等数据类型。
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 mybuflayout可能会随不同的C++编译器而不同,但是这段操作mybufbufC++源代码也能到哪里都工作。如下是这种派生的源代码,它也隐含了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 bufC代码中,能很好的工作,当然,如果给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;
}
 
可以这样定义一个能同时被CC++使用的头文件:
 
#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/OC++的一部分。程序中混合使用IOstream和标准I/O与程序是否含有C代码没有必然联系。
C++标准说可以在同一个目标stream上混合C标准I/OIOstream流,例如标注输入流、标准输出流,这一点不同的C++编译器实现却不尽相同,有的系统要求用户在进行I/O操作前显式地调用sync_with_stdio()。其它还有程序调用性能方面的考虑。
 
6. 如何使用函数指针
 
必须确定一个函数指针究竟是指向C函数还是C++函数。因为CC++函数采用不同的调用约定。如果不明确指针究竟是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型的函数。

抱歉!评论已关闭.