1. 程式人生 > >ffmpeg連結錯誤:關於C++ extern "C"

ffmpeg連結錯誤:關於C++ extern "C"

http://www.360doc.com/content/13/0502/17/9192936_282472350.shtml

在編譯一個ffmepg AAC解碼測試程式時,遇到了如下錯誤輸出:

  1. 1>main.obj : error LNK2001: 無法解析的外部符號 "int __cdecl avcodec_open(struct AVCodecContext *,struct AVCodec *)" ([email protected]@[email protected]@[email protected]@@Z)  
  2. 1>main.obj : error LNK2001: 無法解析的外部符號 "struct AVCodec * __cdecl avcodec_find_decoder(enum CodecID)" (
    [email protected]
    @[email protected]@[email protected]@@Z)  
  3. 1>main.obj : error LNK2001: 無法解析的外部符號 "void __cdecl dump_format(struct AVFormatContext *,int,char const *,int)" ([email protected]@[email protected]@[email protected])  
  4. 1>main.obj : error LNK2001: 無法解析的外部符號 "int __cdecl av_find_stream_info(struct AVFormatContext *)" (
    [email protected]
    @[email protected]@@Z)  
  5. 1>main.obj : error LNK2001: 無法解析的外部符號 "int __cdecl av_open_input_file(struct AVFormatContext * *,char const *,struct AVInputFormat *,int,struct AVFormatParameters *)" ([email protected]@[email protected]@[email protected]@[email protected]
    @@Z)  
  6. 1>main.obj : error LNK2001: 無法解析的外部符號 "struct AVFormatContext * __cdecl av_alloc_format_context(void)" ([email protected]@[email protected]@XZ)  
  7. 1>main.obj : error LNK2001: 無法解析的外部符號 "void __cdecl av_register_all(void)" ([email protected]@YAXXZ)  
  8. 1>main.obj : error LNK2001: 無法解析的外部符號 "void __cdecl av_free(void *)" ([email protected]@[email protected])  
  9. 1>main.obj : error LNK2001: 無法解析的外部符號 "int __cdecl avcodec_close(struct AVCodecContext *)" ([email protected]@[email protected]@@Z)  
  10. 1>main.obj : error LNK2001: 無法解析的外部符號 "int __cdecl avcodec_decode_audio3(struct AVCodecContext *,short *,int *,struct AVPacket *)" ([email protected]@[email protected]@[email protected]@@Z)  
  11. 1>main.obj : error LNK2001: 無法解析的外部符號 "int __cdecl av_read_frame(struct AVFormatContext *,struct AVPacket *)" ([email protected]@[email protected]@[email protected]@@Z)  
  12. 1>main.obj : error LNK2001: 無法解析的外部符號 "void __cdecl av_init_packet(struct AVPacket *)" ([email protected]@[email protected]@@Z)  
  13. 1>main.obj : error LNK2001: 無法解析的外部符號 "void * __cdecl av_malloc(unsigned int)" ([email protected]@[email protected])  

可以確定自己正確連結了lib檔案,為何還會出現連結錯誤呢?更奇怪的是,將呼叫這些外部函式的檔案(test.cpp)改成test.c後,編譯又成功了?哪裡出了問題呢?

問題出在使用在C++中呼叫C語言編寫的動態連結庫時,要在與DLL相應的標頭檔案中新增extern “C”

那為什麼要extern “C”呢?這要從C++與C不同的編譯和連線方式說起:

        C++語言的建立初衷是“a better C”,但是這並不意味著C++中類似C語言的全域性變數和函式所採用的編譯和連線方式與C語言完全相同。作為一種欲與C相容的語言,C++保留了一部分過程式語言的特點(被世人稱為“不徹底地面向物件”),因而它可以定義不屬於任何類的全域性變數和函式。但是,C++畢竟是一種面向物件的程式設計語言,為了支援函式的過載,C++對全域性函式的處理方式與C有明顯的不同。

作為一種面向物件的語言,C++支援函式過載,而過程式語言C則不支援。函式被C++編譯後在符號庫中的名字與C語言的不同。例如,假設某個函式的原型為:

void foo( int x, int y );
  該函式被C編譯器編譯後在符號庫中的名字為_foo,而C++編譯器則會產生像_foo_int_int之類的名字(不同的編譯器可能生成的名字不同,但是都採用了相同的機制,生成的新名字稱為“mangled name”)。

  _foo_int_int這樣的名字包含了函式名、函式引數數量及型別資訊,C++就是靠這種機制來實現函式過載的。例如,在C++中,函式void foo( int x, int y )與void foo( int x, float y )編譯生成的符號是不相同的,後者為_foo_int_float。
  同樣地,C++中的變數除支援區域性變數外,還支援類成員變數和全域性變數。使用者所編寫程式的類成員變數可能與全域性變數同名,我們以"."來區分。而本質上,編譯器在進行編譯時,與函式的處理相似,也為類中的變數取了一個獨一無二的名字,這個名字與使用者程式中同名的全域性變數名字不同。

時常在cpp的程式碼之中看到這樣的程式碼: 

  1. #ifdef __cplusplus  
  2. extern "C" {  
  3. #endif  
  4. //一段程式碼  
  5. #ifdef __cplusplus  
  6. }  
  7. #endif  

  這樣的程式碼到底是什麼意思呢?首先,__cplusplus是cpp中的自定義巨集,那麼定義了這個巨集的話表示這是一段cpp的程式碼,也就是說,上面的程式碼的含義是:如果這是一段cpp的程式碼,那麼加入extern "C"{和}處理其中的程式碼。

  要明白為何使用extern "C",還得從cpp中對函式的過載處理開始說起。在c++中,為了支援過載機制,在編譯生成的彙編碼中,要對函式的名字進行一些處理,加入比如函式的返回型別等等.而在C中,只是簡單的函式名字而已,不會加入其他的資訊.也就是說:C++和C對產生的函式名字的處理是不一樣的.

  比如下面的一段簡單的函式,我們看看加入和不加入extern "C"產生的彙編程式碼都有哪些變化:

  1. int f(void)  
  2. {  
  3. return 1;  
  4. }  
  在加入extern "C"的時候產生的彙編程式碼是:
  1. .file "test.cxx"  
  2. .text  
  3. .align 2  
  4. .globl _f  
  5. .def _f; .scl 2; .type 32; .endef  
  6. _f:  
  7. pushl %ebp  
  8. movl %esp, %ebp  
  9. movl $1, %eax  
  10. popl %ebp  
  11. ret  
  但是不加入了extern "C"之後
  1. .file "test.cxx"  
  2. .text  
  3. .align 2  
  4. .globl __Z1fv  
  5. .def __Z1fv; .scl 2; .type 32; .endef  
  6. __Z1fv:  
  7. pushl %ebp  
  8. movl %esp, %ebp  
  9. movl $1, %eax  
  10. popl %ebp  
  11. ret  
  兩段彙編程式碼同樣都是使用gcc -S命令產生的,所有的地方都是一樣的,唯獨是產生的函式名,一個是_f,一個是__Z1fv。

  明白了加入與不加入extern "C"之後對函式名稱產生的影響,我們繼續我們的討論:為什麼需要使用extern "C"呢?C++之父在設計C++之時,考慮到當時已經存在了大量的C程式碼,為了支援原來的C程式碼和已經寫好C庫,需要在C++中儘可能的支援C,而extern "C"就是其中的一個策略。

  試想這樣的情況:一個庫檔案已經用C寫好了而且執行得很良好,這個時候我們需要使用這個庫檔案,但是我們需要使用C++來寫這個新的程式碼。如果這個程式碼使用的是C++的方式連結這個C庫檔案的話,那麼就會出現連結錯誤.我們來看一段程式碼:首先,我們使用C的處理方式來寫一個函式,也就是說假設這個函式當時是用C寫成的:
  1. //f1.c  
  2. extern "C"  
  3. {  
  4. void f1()  
  5. {  
  6. return;  
  7. }  
  8. }  
  編譯命令是:gcc -c f1.c -o f1.o 產生了一個叫f1.o的庫檔案。再寫一段程式碼呼叫這個f1函式:
  1. // test.cxx  
  2. //這個extern表示f1函式在別的地方定義,這樣可以通過  
  3. //編譯,但是連結的時候還是需要  
  4. //連結上原來的庫檔案.  
  5. extern void f1();  
  6. int main()  
  7. {  
  8. f1();  
  9. return 0;  
  10. }  
  通過gcc -c test.cxx -o test.o 產生一個叫test.o的檔案。然後,我們使用gcc test.o f1.o來連結兩個檔案,可是出錯了,錯誤的提示是:
  1. test.o(.text + 0x1f):test.cxx: undefine reference to 'f1()'  
  也就是說,在編譯test.cxx的時候編譯器是使用C++的方式來處理f1()函式的,但是實際上鍊接的庫檔案卻是用C的方式來處理函式的,所以就會出現連結過不去的錯誤:因為連結器找不到函式。

  因此,為了在C++程式碼中呼叫用C寫成的庫檔案,就需要用extern "C"來告訴編譯器:這是一個用C寫成的庫檔案,請用C的方式來連結它們。

  比如,現在我們有了一個C庫檔案,它的標頭檔案是f.h,產生的lib檔案是f.lib,那麼我們如果要在C++中使用這個庫檔案,我們需要這樣寫:
  1. extern "C"  
  2. {  
  3. #include "f.h"  
  4. }  
  回到上面的問題,如果要改正連結錯誤,我們需要這樣子改寫test.cxx:
  1. extern "C"  
  2. {  
  3. extern void f1();  
  4. }  
  5. int main()  
  6. {  
  7. f1();  
  8. return 0;  
  9. }  

  重新編譯並且連結就可以過去了.

  總結


  C和C++對函式的處理方式是不同的.extern "C"是使C++能夠呼叫C寫作的庫檔案的一個手段,如果要對編譯器提示使用C的方式來處理函式的話,那麼就要使用extern "C"來說明。