我来理解extern “C”

编程快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一定是大写的.

版权所有,禁止转载. 如需转载,请先征得博主的同意,并且表明文章出处,否则按侵权处理.

    分享到:

2 thoughts on “我来理解extern “C”

Leave a Reply

Your email address will not be published. Required fields are marked *