编程快5年了, 今天才算对extern “C” 有个较为清晰的认识. 本人对extern “C”的认识可以分为三个阶段:
1. 从别人的代码里面看到有这个东西, 自己不认识, 就上网查查, 大概明了它是什么意思.
2. 看到别人的代码里面包含用c写的代码就用extern “C”, 那么自己依葫芦画瓢用c代码的时候也加这个东西. 自己感觉对这个东西很熟悉了.
3. 编译链接代码死活过不去, 一点点分析. 才发现需要这个东西. 现在才比较明了这个东西了.
下面先说说, 这个问题是怎么出来的.
最近在研究ffmpeg, 想把它整合到vc的工程中. 建了一个win32的console工程. 创建一个main.cpp. 这个文件中的实现的非常简单. 如下:
#include “libavformat/avformat.h”
#include “libswscale/swscale.h”
int main( int argc, char **argv )
{
cout<<“this is string”<<endl;
const char *filename;
AVOutputFormat *fmt;
AVFormatContext *oc;
AVStream *audio_st, *video_st;
double audio_pts, video_pts;
int i;
/* initialize libavcodec, and register all codecs and formats */
av_register_all();
}
看到了吧, 就是调用了avformat.h的一个函数.
接下来, 编译…
出错了,
main.obj : error LNK2001: unresolved external symbol “void __cdecl av_register_all(void)” (?av_register_all@@YAXXZ)
错在链接的时候, 我们看看是怎么一个过程. 通过编译器编译(这里的编译器是c++编译器, 因为我们的文件以cpp结尾). main函数中 av_register_all(); 变成了?av_register_all@@YAXXZ. 接下来链接器查找这些符号, 如果找到就对这个函数的调用换成对应的地址. 如果没有找到, 我们就会看到刚才的那个错误. 链接器查找的过程是, 现在本文件中找, 接下来是所有本工程中已被编译的.obj(c++对应.obj, 而c对应.o,), 最后在已导入工程的lib文件中找.
这里打个岔, 编译工程的时候, 我们开发环境怎么来决定用c编译器还是c++编译器, 一般可以可以由文件的后缀来决定. 也就是说, 以.c为后缀的源文件, 用c编译器编译. 以.cpp为后缀的则用c++编译器. 但这不是绝对的. 这个前提是你在工程设置里面选的是default, 在vs2005中你可以在project->properties->c/c++->advance的complie as找到对应的值, 这里的值如果你没有改过, 那就是default. 你可以改成你想要的方式complied as c++, 或者改成complied as c. 或者直接在在编译选项添加/TP, /TC. 前者表示c++, 后者是C.
回到我们的这个工程, (我已经在导入了这个函数对应的库文件: avformat.lib.) 链接器链接的时候, 在main中没有找到(肯定找不到, 因为没有定义它), 在工程中的.obj中找, 当然找不到了, 因为我们只有一个.obj文件, 那就是编译的main.obj. 最后在导入的库中找, 似乎在这里可以找到, 但是为什么链接器没有找到呢?
原因是avformat.lib是c编译器编译链接的文件, av_register_all被编译后是_av_register_all@ . 显然在这个库中我们的c++的链接器找不到?av_register_all@@YAXXZ. 就有了那个错误了.
那我们怎么做, 才能避免c++把这个函数的调用编译成?av_register_all@@YAXXZ.. 这个时候我们的extern “C”就有用武之地了. 修改的方法就是在包含文件的地方用:
extern “C” {
#include “libavformat/avformat.h”
#include “libswscale/swscale.h”
}
再一次编译. 呵呵, 过了.
除此之外, 我们还可以直接用extern “C” 来声明这个函数, 替代上面的include部分.
extern “C”{
void av_register_all( void );
}
或者直接:
extern “C” void av_register_all( void );
但是如果用到这个库中很多很多的函数, 那我们用#include头文件的方式. 不用写一大堆函数声明. 从这里也看到头文件的一个作用.
在c编译的情况下就没有必要这么做了, 因此我们来个条件编译, 变成这样:
#ifdef __cplusplus
extern “C” {
#include “libavformat/avformat.h”
#include “libswscale/swscale.h”
}
#endif // __cplusplus
问题解决. 也算比较清晰的认识了extern “C”. 注意这里的C一定是大写的.
版权所有,禁止转载. 如需转载,请先征得博主的同意,并且表明文章出处,否则按侵权处理.
不错,我也正好碰到这个问题
解释非常透彻,赞一个!