C和C++混合编程
在很多情况下,C系统中会用到C++的代码,而C++中也会用到C代码。所以在Linux系统下充斥中.h .c .cpp后缀的代码文件。这样就引入了一个问题:如何让这些代码很好的在一起编译链接。在这几天项目中确实也遇到了相似的问题,把一些问题和解决办法记录下来以共勉。
首先要说明的是在C语言代码中,必须做好C++编译器的编译预处理,及严格按照下面的模式来写.h头文件。
#ifdef _XXX_H
#ifdef __cplusplus
extern “C” {
#endif
// Routines Here
#ifdef __cplusplus
}
#endif
#endif
上面在extern 时,目前GCC和G++编译器都可以进行识别,但要扩展支持其它的编译器的标准,例如几种主流编译器,C++ compilers, ANSI C compilers和non-ANSI C compilers,要完全严格的支持,必须按照前面一篇“对__BEGIN_DECLS 和 __END_DECLS 的理解”中描述的采用
# define __BEGIN_DECLS extern "C" {
进行严格的规范书写代码,可以安全被各种编译器使用的.h文件。
如果所有的.h头文件都是按照extern来做C的条件编译,后面会少很多编译上的问题。
下面从C和c++互调的角度对两种情空阐述。
1. .cpp调用 .c文件
Cpp中调用C语言代码中的不外乎下面三种:
变量
函数
数据类型
如果原来C代码写得很好的话,直接include相应的.h文件即可。退一步,如果.h没有很好的去写的话,要在CPP使用的地方(Cpp对应的.h头文件或是实现代码文件.cpp中都可以)做extern #include,具体对于.h和.cpp不同:
.h文件中代码:
#ifdef __cpluslus
extern “C” #include “xxx.h”
#endif
.cpp文件中代码:
extern “C” #include “xxx.h”
上面这么不同的根本原因是由于.h是.c和.cpp公用的,只有在被.c和.cpp代码include进来在进行编译的时候才被编译链接,本身是不会被编译的。而.cpp中当然已经是G++编译器来编译,GCC中没有定义 __cplusplus而g++中有这个宏的定义。
上面的是理想的情况,即C代码中严格按照g++编译器的标准去实现,典型的有:
void* 不向其它类型的指针转化
const变量声明的时候要初始化等
用GCC编译通过的代码不一定能被G++编译器编译通过,因为G++是一种更为严格的编译器。
那么,在这种情况下,如果不想改变原来的C代码(在很多情况下,不方便或是不能更改代码),只要自己改变自己的控制能力了。一种比较好的解决方法就是,CPP中用什么extern什么,这样在很大程度上免去了include .c对应的头文件,而头文件又include其他不规范的头文件的层次包含导致的链接错误。
变量 extern valtype instance;
函数 extern “C” void f(void*, …); //在.h和.cpp不同,上面已经说明
数据类型 比如struct,直接写 struct st_xxx;
这样在最小范围内进行混合的编译链接,减少很多的编译麻烦。
2. .c调用 .cpp文件
一般情况下,.c调用.cpp会有两种情况:
*对cpp中class用C语言封装,这样相当于用C来模拟C++代码;
*对cpp进行包装,用另外一个.h和.cpp来对class的逻辑进行封装。
对应第一种情况,这样的需求很少,但确实有这么干的。下面说说这种情况下的解决方案。
对于每个class文件,同时创建对应的Ixxx.h和ICxxx.h文件。下面通过一个头文件可以看出大致整个系统中如何去改写。
#ifndef _VNL_H_
#define _VNL_H_
#ifdef __cplusplus
#include "CITest.h"
#else
#include "ITest.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
Itest *ITest_new(void);
#ifdef __cplusplus
}
#endif
#endif
这个文件中包含二个头文件Itest.h CITest.h,都是新创建的文件。因为要统一编译Itest类型,而这个类在CPP中是Ctest,要想统一兼容,创建一个ITest的头文件,而此时CPP中的Ctest要和ITest关联起来,所以要再创建一个新的CITest.h文件。具体的各个文件中内容如下:
#include "Ctest.h"
#include
class CTest: public CTestBase
{
public:
virtual int OPR(const char *msg) = 0;
CTest();
virtual ~ CTest ();
};
#ifndef _CITEST_H_
#define _CITEST_H_
#include "CITestBase.h"
class ITest : public ITestBase
{
public:
virtual int OPR(const char *msg) = 0;
};
#endif
#ifndef _ITEST_H_
#define _ITEST_H_
#include "ItestBase.h"
typedef struct OPTest_st OPTest_t;
typedef struct ITest_st ITest; //Itest使得两者关联起来
struct OPTest_st {
int (*OPR)(ITest *this, const char *msg);
};
struct ITest_st {
OPTest_t *ops;
};
#define ITest_OPR(test, msg) (test)->ops->OPR((test), (msg))
#endif
在使用的时候,只要包括vnl.h即可通过编译开关选择来然编译器自动确定。
当然,上面这种做法是比较辛苦的做法。如果对所需要的函数进行封装,可以达到效果。具体作法如下:
1. 创建一个.h和.cpp文件。
这个.h文件是标准的C编译器可以编译通过的头文件,声明可以暴露给外部使用的函数。相应的.cpp文件中实现头文件中的函数。Cpp中的文件当然可以写一些在.h文件中没有的函数,例如对参数中包括类等C语言无法识别的语法的语句。
2. 在.c文件中include 上面的标准.h头文件。
至此,基本的C和c++中互调的情况就这么多。后面可能会遇到更多的情况,再做补充。
关于extern和C C++互调的补充介绍可以参考附件.
具体阐述C语言中extern,国外一个论坛的帖子。
总概括起来,分变量和函数:
变量:一个地方定义,其它地方使用时加extern。
但是如果:
a. 其它使用的地方忘记加extern (重复定义)
b. 原先定义的地方加extern (没有定义)
c. 其它使用的地方多了对external后的变量赋值 (重复定义)
将变量写在cpp文件中,在头文件中就可以extern所需要的变量,当包括这个头文件时候就可用使用这些extern变量(extern ...).
函数:默认情况就是extern,当extern cpp文件中定义的变量时编译器按照extern(符号识别及标记),变量定义(分配空间)来编译。
C和C++互调,在sun公司文档资料库中有些上面没讨论到的部分内容的讨论。
- Using Compatible Compilers
- Accessing C Code From Within C++ Source
c++语言可以链接的时候指定,所以导致许多c++编译器在编译的时候可以构造不同的编译选项。例如:extern "C"/ extern "C++".
在C中增加c++类似的函数,例如virtual函数,此时可以通过先将结构在.h中声明好,然后通过在c++头文件中:public xxx_st的方式来在
c++代码中使用xxx_st中声明的函数。
- Accessing C++ Code From Within C Source
要确保extern C是“纯C”。还有就是前面提到的,C不支持overload,多个要进行函数变得以区分。
C中使用c++的函数时,在C++中增加extern "C"的函数以访问c++中类的函数,然后在C中声明包装的函数和不完全的声明与类名相同的结构名
,增加一个函数去调用C++中extern的函数。具体的例子见下面的具体的章节。
- Mixing IOstream and C Standard I/O
- Working with Pointers to Functions
- Working with C++ Exceptions
- Linking the Program
在c++中链接,函数指针必须链接与之匹配的函数。
对于:
typedef int (*pfn)(int);
extern "C" void foo( int (*)(int) ); // declaration C调用c
会导致与extern "C" void foo(pfn p) { ... } // definition C调用C++
重载的问题。可以通过typedef extern来解决。
http://developers.sun.com/solaris/articles/mixing.html
附件1:
Let's have 3 files: badguy.cpp, extras.cpp, and main.cpp. Remember all these files are is text files with a special extension. The compiler doesn't care about this extension at all*.
Let's say extras.cpp has a variable that you want to use:
// extras.cpp
BITMAP* myBitmap = NULL;
This is called defining a variable. If we want to use this variable in main.cpp, you use "extern":
// main.cpp
extern BITMAP* myBitmap;
This is called declaring a variable. The difference is that in defining a variable, the compiler allocates space for it in the program's code, whereas in declaring it, the compiler simply learns that there is a variable by that name defined elsewhere.
As you already know, "extern" tells the compiler "a variable with the name myBitmap exists, but I am not defining it here. You will get more information about this variable later on".
When you compile this, the compiler compiles main.cpp into main.o and makes a note: "I need to find the variable myBitmap". When it links main.o and extras.o together (the process that takes the compiled .o files and links them into an executable file), the linker notices that main.o needs to know about myBitmap, which it finds in extras.o. The program is linked, and the compilation is successful.
Now, when badguy.cpp also needs to use myBitmap, we do the same thing:
// badguy.cpp
extern BITMAP* myBitmap;
The same process as above happens for badguy.o, and the compilation is successful.
That's the correct usage of extern. Here is the incorrect usage:
What if you forget to declare a variable "extern" once? Like, instead of the badguy.cpp posted above, use this one instead.
// badguy.cpp revision 2 (does not link)
BITMAP* myBitmap;
When the compiler compiles this file, it knows that it has a variable called myBitmap and it allocates space for it in the program's code. When the linker comes to link all of the .o files together, it sees that there is a myBitmap in badguy.o, and a myBitmap in extras.o, and it gives a "multiple definition" error. So, failing to use extern causes multiple definition errors.
What if all of the source files declare the variable extern? That is, using this version of extras.cpp (and the first version of badguy.cpp):
// extras.cpp revision 2 (does not link)
extern BITMAP* myBitmap;
When the linker goes to link all of the source files together, it sees that in all 3 files the variable myBitmap is declared someplace else. If it doesn't find any actual myBitmap variable (which is what happens in this example), the linker errors with an "undefined reference" error. So, using extern everywhere and failing to ever define the variable causes undefined reference errors.
One more thing. Only the definition of a variable can give it a value:
// badguy.cpp revision 3 (does not compile)
extern BITMAP* myBitmap = NULL;
The compiler will see that line, emit a warning that an extern variable was initialized, but the compiler will treat the variable as though it weren't extern at all. This can cause "multiple definition" errors, as above.
That's how not to use extern. How do header files come into this?
Obviously, if you had 10 or 15 variables in badguy.cpp, you wouldn't want to list them all in every source file that used them. To help this problem, we have "header files". Remember, all of the files we are dealing with are just text files. The extension on the file name is simply so we as programmers know the nature of the content in side the file. Let's create an extras.h file for our extras.cpp:
// extras.h
extern BITMAP* myBitmap;
Then, let's modify our 3 other files to use this include file:
// extras.cpp revision 3
#include "extras.h"
// Beginning of extras.cpp code (#include finished)
BITMAP* myBitmap;
// main.cpp revision 2
#include "extras.h"
// badguy.cpp revision 3
#include "extras.h"
When the compiler goes to compile these files, it preprocesses them before it compiles them. If you create the four files shown above and run "gcc -E extras.cpp", you will see the output of the preprocessor. This is what you would see: (technically, the comments would be removed from the result file, I'm keeping them for clarity.
// extras.cpp revision 3
// extras.h
extern BITMAP* myBitmap;
// Beginning of extras.cpp code (#include finished)
BITMAP* myBitmap;
You see? The compiler just flat-out copied the contents of the include file straight into the line where #include happened. In all the other source files, they end up looking exactly like they did before. This one looks a little different, though. What happens when the compiler compiles it?
The compiler reads the "extern BITMAP* myBitmap" line, so it knows there is a variable myBitmap that code can use, but it will be defined (space will be made for it) later. Then it gets to the "BITMAP* myBitmap" line, and actually makes space for that variable. The compilation is successful.
How does "extern" apply to functions?
Sometimes you will see function declarations in header files, also:
// extras.h revision 2
extern BITMAP* myBitmap;
void loadMyBitmap();
That last line tells the compiler about (it "declares") a function called loadMyBitmap, that will be defined somewhere else in the code. Then, extras.cpp comes along and actually defines the function:
// extras.cpp revision 4
#include "extras.h"
BITMAP* myBitmap;
void loadMyBitmap()
{
myBitmap = load_bitmap("image.bmp", NULL);
}
The same process happens as for variables, and the compilation is successful. Take note: these two lines of code are entirely equivalent:
void loadMyBitmap();
extern void loadMyBitmap();
Phrased differently, functions are declared extern by default.
I think I've covered everything I wanted to about extern, and this should give you a more technical explanation of what extern and #include are doing.
Technically, it does care about them, but not for anything discussed in this post
附件2:
http://developers.sun.com/solaris/articles/mixing.html
Article: Mixing C and C++ Code in the Same Program
Print-friendly VersionPrint-friendly Version
By Stephen Clamage, Sun Microsystems, Sun ONE Studio Solaris Tools Development Engineering
The C++ language provides mechanisms for mixing code that is compiled by compatible C and C++ compilers in the same
program. You can experience varying degrees of success as you port such code to different platforms and compilers. This
article shows how to solve common problems that arise when you mix C and C++ code, and highlights the areas where you might
run into portability issues. In all cases we show what is needed when using Sun C and C++ compilers.
Contents
- Using Compatible Compilers
- Accessing C Code From Within C++ Source
c++语言可以链接的时候指定,所以导致许多c++编译器在编译的时候可以构造不同的编译选项。例如:extern "C"/ extern "C++".
在C中增加c++类似的函数,例如virtual函数,此时可以通过先将结构在.h中声明好,然后通过在c++头文件中:public xxx_st的方式来在
c++代码中使用xxx_st中声明的函数。
- Accessing C++ Code From Within C Source
要确保extern C是“纯C”。还有就是前面提到的,C不支持overload,多个要进行函数变得以区分。
C中使用c++的函数时,在C++中增加extern "C"的函数以访问c++中类的函数,然后在C中声明包装的函数和不完全的声明与类名相同的结构名
,增加一个函数去调用C++中extern的函数。具体的例子见下面的具体的章节。
- Mixing IOstream and C Standard I/O
- Working with Pointers to Functions
- Working with C++ Exceptions
- Linking the Program
在c++中链接,函数指针必须链接与之匹配的函数。
对于:
typedef int (*pfn)(int);
extern "C" void foo( int (*)(int) ); // declaration C调用c
会导致与extern "C" void foo(pfn p) { ... } // definition C调用C++
重载的问题。可以通过typedef extern来解决。
Using Compatible Compilers
The first requirement for mixing code is that the C and C++ compilers you are using must be compatible. They must, for
example, define basic types such as int, float or pointer in the same way. The Solaris Operating System (Solaris OS)
specifies the Application Binary Interface (ABI) of C programs, which includes information about basic types and how
functions are called. Any useful compiler for the Solaris OS must follow this ABI.
Sun C and C++ compilers follow the Solaris OS ABI and are compatible. Third-party C compilers for the Solaris OS usually
also follow the ABI. Any C compiler that is compatible with the Sun C compiler is also compatible with the Sun C++
compiler.
The C runtime library used by your C compiler must also be compatible with the C++ compiler. C++ includes the standard C
runtime library as a subset, with a few differences. If the C++ compiler provides its own versions of of the C headers, the
versions of those headers used by the C compiler must be compatible.
Sun C and C++ compilers use compatible headers, and use the same C runtime library. They are fully compatible.
Accessing C Code From Within C++ Source
The C++ language provides a "linkage specification" with which you declare that a function or object follows the program
linkage conventions for a supported language. The default linkage for objects and functions is C++. All C++ compilers also
support C linkage, for some compatible C compiler.
When you need to access a function compiled with C linkage (for example, a function compiled by the C compiler), declare
the function to have C linkage. Even though most C++ compilers do not have different linkage for C and C++ data objects,
you should declare C data objects to have C linkage in C++ code. With the exception of the pointer-to-function type, types
do not have C or C++ linkage.
Declaring Linkage Specifications
Use one of the following notations to declare that an object or function has the linkage of language language_name:
extern "language_name" declaration ;
extern "language_name" { declaration ; declaration ; ... }
The first notation indicates that the declaration (or definition) that immediately follows has the linkage of
language_name. The second notation indicates that everything between the curly braces has the linkage of language_name,
unless declared otherwise. Notice that you do not use a semicolon after the closing curly brace in the second notation.
You can nest linkage specifications, but they do not create a scope. Consider the following example:
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
}
All the functions above are in the same global scope, despite the nested linkage specifiers.
Including C Headers in C++ Code
If you want to use a C library with its own defining header that was intended for C compilers, you can include the header
in extern "C" brackets:
extern "C" {
#include "header.h"
}
Warning-
Do not use this technique for system headers on the Solaris OS. The Solaris headers, and all the headers that come with Sun
C and C++ compilers, are already configured for use with C and C++ compilers. You can invalidate declarations in the
Solaris headers if you specify a linkage.
Creating Mixed-Language Headers
If you want to make a header suitable for both C and C++ compilers, you could put all the declarations inside extern "C"
brackets, but the C compiler does not recognize the syntax. Every C++ compiler predefines the macro __cplusplus, so you can
use that macro to guard the C++ syntax extensions:
#ifdef __cplusplus
extern "C" {
#endif
... /* body of header */
#ifdef __cplusplus
} /* closing brace for extern "C" */
#endif
Adding C++ features to C structs
Suppose you want to make it easier to use a C library in your C++ code. And suppose that instead of using C-style access
you might want to add member functions, maybe virtual functions, possibly derive from the class, and so on. How can you
accomplish this transformation and ensure the C library functions can still recognize the struct? Consider the uses of the
C struct buf in the following example:
struct buf {
char* data;
unsigned count;
};
void buf_clear(struct buf*);
int buf_print(struct buf*); /* return status, 0 means fail */
int buf_append(struct buf*, const char*, unsigned count); /* same return */
You want to turn this struct into a C++ class and make it easier to use with the following changes:
extern "C" {
#include "buf.h"
}
class mybuf { // first attempt -- will it work?
public:
mybuf() : data(0), count(0) { }
void clear() { buf_clear((buf*)this); }
bool print() { return buf_print((buf*)this); }
bool append(const char* p, unsigned c)
{ return buf_append((buf*)this, p, c); }
private:
char* data;
unsigned count;
};
The interface to the class mybuf looks more like C++ code, and can be more easily integrated into an Object-Oriented style
of programming -- if it works.
What happens when the member functions pass the this pointer to the buf functions? Does the C++ class layout match the C
layout? Does the this pointer point to the data member, as a pointer to buf does? What if you add virtual functions to
mybuf?
The C++ standard makes no promises about the compatibility of buf and class mybuf. This code, without virtual functions,
might work, but you can't count on it. If you add virtual functions, the code will fail using compilers that add extra data
(such as pointers to virtual tables) at the beginning of a class.
The portable solution is to leave struct buf strictly alone, even though you would like to protect the data members and
provide access only through member functions. You can guarantee C and C++ compatibility only if you leave the declaration
unchanged.
You can derive a C++ class mybuf from the C struct buf, and pass pointers to the buf base class to the mybuf functions. If
a pointer to mybuf doesn't point to the beginning of the buf data, the C++ compiler will adjust it automatically when
converting a mybuf* to a buf*. The layout of mybuf might vary among C++ compilers, but the C++ source code that manipulates
mybuf and buf objects will work everywhere. The following example shows a portable way to add C++ and Object-Oriented
features to a 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++ code can freely create and use mybuf objects, passing them to C code that expects buf objects, and everything will work
together. Of course, if you add data to mybuf, the C code won't know anything about it. That's a general design
consideration for class hierarchies. You also have to take care to create and delete buf and mybuf objects consistently. It
is safest to let C code delete (free) an object if it was created by C code, and not allow C code to delete a mybuf object.
Accessing C++ Code From Within C Source
If you declare a C++ function to have C linkage, it can be called from a function compiled by the C compiler. A function
declared to have C linkage can use all the features of C++, but its parameters and return type must be accessible from C if
you want to call it from C code. For example, if a function is declared to take a reference to an IOstream class as a
parameter, there is no (portable) way to explain the parameter type to a C compiler. The C language does not have
references or templates or classes with C++ features.
Here is an example of a C++ function with C linkage:
#include
extern "C" int print(int i, double d)
{
std::cout << "i = " << i << ", d = " << d;
}
You can declare function print in a header file that is shared by C and C++ code:
#ifdef __cplusplus
extern "C"
#endif
int print(int i, double d);
You can declare at most one function of an overloaded set as extern "C" because only one C function can have a given name.
If you need to access overloaded functions from C, you can write C++ wrapper functions with different names as the
following example demonstrates:
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); }
Here is the example C header for the wrapper functions:
int g_int(int);
double g_double(double);
You also need wrapper functions to call template functions because template functions cannot be declared as extern "C":
template 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); }
C++ code can still call the the overloaded functions and the template functions. C code must use the wrapper functions.
Accessing C++ Classes From C
Can you access a C++ class from C code? Can you declare a C struct that looks like a C++ class and somehow call member
functions? The answer is yes, although to maintain portability you must add some complexity. Also, any modifications to the
definition of the C++ class you are accessing requires that you review your C code.
Suppose you have a C++ class such as the following:
class M {
public:
virtual int foo(int);
// ...
private:
int i, j;
};
You cannot declare class M in your C code. The best you can do is to pass around pointers to class M objects, similar to
the way you deal with FILE objects in C Standard I/O. You can write extern "C" functions in C++ that access class M objects
and call them from C code. Here is a C++ function designed to call the member function foo:
extern "C" int call_M_foo(M* m, int i) { return m->foo(i); }
Here is an example of C code that uses class M:
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); }
Mixing IOstream and C Standard I/O
You can use C Standard I/O, from the standard C header , in C++ programs because C Standard I/O is part of C++.
Any considerations about mixing IOstream and Standard I/O in the same program therefore do not depend on whether the
program contains C code specifically. The issues are the same for purely C++ programs that use both Standard I/O and
IOstreams.
Sun C and C++ use the same C runtime libraries, as noted in the section about compatible compilers. Using Sun compilers,
you can therefore use Standard I/O functions freely in both C and C++ code in the same program.
The C++ standard says you can mix Standard I/O functions and IOstream functions on the same target "stream", such as the
standard input and output streams. But C++ implementations vary in their compliance. Some systems require that you call the
sync_with_stdio() function explicitly before doing any I/O. Implementations also vary in the efficiency of I/O when you mix
I/O styles on the same stream or file. In the worst case, you get a system call per character input or output. If the
program does a lot of I/O, the performance might be unacceptable.
The safest course is to stick with Standard I/O or IOstream styles on any given file or standard stream. Using Standard I/O
on one file or stream and IOstream on a different file or stream does not cause any problems.
Working with Pointers to Functions
A pointer to a function must specify whether it points to a C function or to a C++ function, because it is possible that C
and C++ functions use different calling conventions. Otherwise, the compiler does not know which kind of function-calling
code to generate. Most systems do not have have different calling conventions for C and C++, but C++ allows for the
possibility. You therefore must be careful about declaring pointers to functions, to ensure that the types match. Consider
the following example:
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
Line 1 declares pfun to point to a C++ function, because it lacks a linkage specifier.
Line 2 therefore declares foo to be a C function that takes a pointer to a C++ function.
Line 5 attempts to call foo with a pointer to g, a C function, a type mis-match.
Be sure to match the linkage of a pointer-to-function with the functions to which it will point. In the following corrected
example, all declarations are inside extern "C" brackets, ensuring that the types match.
extern "C" {
typedef int (*pfun)(int);
void foo(pfun);
int g(int);
}
foo( g ); // now OK
Pointers to functions have one other subtlety that occasionally traps programmers. A linkage specification applies to all
the parameter types and to the return type of a function. If you use the elaborated declaration of a pointer-to-function in
a function parameter, a linkage specification on the function applies to the pointer-to-function as well. If you declare a
pointer-to-function using a typedef, the linkage specification of that typedef is not affected by using it in a function
declaration. For example, consider this code:
typedef int (*pfn)(int);
extern "C" void foo(pfn p) { ... } // definition
extern "C" void foo( int (*)(int) ); // declaration
The first two lines might appear in a program file, and the third line might appear in a header where you don't want to
expose the name of the private typedef. Although you intended for the declaration of foo and its definition to match, they
do not. The definition of foo takes a pointer to a C++ function, but the declaration of foo takes a pointer to a C
function. The code declares a pair of overloaded functions.
To avoid this problem, use typedefs consistently in declarations, or enclose the typedefs in appropriate linkage
specifications. For example, assuming you wanted foo to take a pointer to a C function, you could write the definition of
foo this way:
extern "C" {
typedef int (*pfn)(int);
void foo(pfn p) { ... }
}
Working with C++ Exceptions
Propagating Exceptions
What happens if you call a C++ function from a C function, and the C++ function throws an exception? The C++ standard is
somewhat vague about whether you can expect exceptions to behave properly, and on some systems you have to take special
precautions. Generally, you must consult the user manuals to determine whether the code will work properly.
No special precautions are necessary with Sun C++. The exception mechanism in Sun C++ does not affect the way functions are
called. If a C function is active when a C++ exception is thrown, the C function is passed over in the process of handling
the exception.
Mixing Exceptions with set_jmp and long_jmp
The best advice is not to use long_jmp in programs that contain C++ code. The C++ exception mechanism and C++ rules about
destroying objects that go out of scope are likely to be violated by a long_jmp, with unpredictable results. Some compilers
integrate exceptions and long_jmp, allowing them to work together, but you should not depend on such behavior. Sun C++ uses
the same set_jmp and long_jmp as the C compiler.
Many C++ experts believe that long_jmp should not be integrated with exceptions, due to the difficulty of specifying
exactly how it should behave.
If you use long_jmp in C code that you are mixing with C++, ensure that a long_jmp does not cross over an active C++
function. If you cannot ensure that, see if you can compile that C++ code with exceptions disabled. You still might have
problems if the destructors of local objects are bypassed.
Linking the Program
At one time, most C++ compilers required that function main be compiled by the C++ compiler. That requirement is not common
today, and Sun C++ does not require it. If your C++ compiler needs to compile the main function but you cannot do so for
some reason, you can change the name of the C main function and call it from a wrapper version of C++ main. For example,
change the name of the C main function to C_main, and write this C++ code:
extern "C" int C_main(int, char**); // not needed for Sun C++
int main(int argc, char** argv) { return C_main(argc, argv); }
Of course, C_main must be declared in the C code to return an int, and it will have to return an int value. As noted above,
you do not need to go to this trouble with Sun C++.
Even if your program is primarily C code but makes use of C++ libraries, you need to link C++ runtime support libraries
provided with the C++ compiler into your program. The easiest and best way to do that is to use the C++ compiler driver to
do the linking. The C++ compiler driver knows what libraries to link, and the order in which to link them. The specific
libraries can depend on the options used when compiling the C++ code.
Suppose you have C program files main.o, f1.o, and f2.o, and you use a C++ library helper.a. With Sun C++, you would issue
the command
CC -o myprog main.o f1.o f2.o helper.a
The necessary C++ runtime libraries like libCrun and libCstd are linked automatically. The documentation for helper.a might
require that you use additional link-time options. If you can't use the C++ compiler for some reason, you can use the -
dryrun option of the CC command to get the list of commands the compiler issues, and capture them into a shell script.
Since the exact commands depend on command-line options, you should review the output from -dryrun with any change of the
阅读(1661) | 评论(0) | 转发(0) |