ffmpeg連結錯誤:關於C++ extern "C"
http://www.360doc.com/content/13/0502/17/9192936_282472350.shtml
在編譯一個ffmepg AAC解碼測試程式時,遇到了如下錯誤輸出:
- 1>main.obj : error LNK2001: 無法解析的外部符號 "int __cdecl avcodec_open(struct AVCodecContext *,struct AVCodec *)" ([email protected]@[email protected]@[email protected]@@Z)
- 1>main.obj : error LNK2001: 無法解析的外部符號 "struct AVCodec * __cdecl avcodec_find_decoder(enum CodecID)" (
- 1>main.obj : error LNK2001: 無法解析的外部符號 "void __cdecl dump_format(struct AVFormatContext *,int,char const *,int)" ([email protected]@[email protected]@[email protected])
- 1>main.obj : error LNK2001: 無法解析的外部符號 "int __cdecl av_find_stream_info(struct AVFormatContext *)" (
- 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]
- 1>main.obj : error LNK2001: 無法解析的外部符號 "struct AVFormatContext * __cdecl av_alloc_format_context(void)" ([email protected]@[email protected]@XZ)
- 1>main.obj : error LNK2001: 無法解析的外部符號 "void __cdecl av_register_all(void)" ([email protected]@YAXXZ)
- 1>main.obj : error LNK2001: 無法解析的外部符號 "void __cdecl av_free(void *)" ([email protected]@[email protected])
- 1>main.obj : error LNK2001: 無法解析的外部符號 "int __cdecl avcodec_close(struct AVCodecContext *)" ([email protected]@[email protected]@@Z)
- 1>main.obj : error LNK2001: 無法解析的外部符號 "int __cdecl avcodec_decode_audio3(struct AVCodecContext *,short *,int *,struct AVPacket *)" ([email protected]@[email protected]@[email protected]@@Z)
- 1>main.obj : error LNK2001: 無法解析的外部符號 "int __cdecl av_read_frame(struct AVFormatContext *,struct AVPacket *)" ([email protected]@[email protected]@[email protected]@@Z)
- 1>main.obj : error LNK2001: 無法解析的外部符號 "void __cdecl av_init_packet(struct AVPacket *)" ([email protected]@[email protected]@@Z)
- 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的程式碼之中看到這樣的程式碼:
- #ifdef __cplusplus
- extern "C" {
- #endif
- //一段程式碼
- #ifdef __cplusplus
- }
- #endif
這樣的程式碼到底是什麼意思呢?首先,__cplusplus是cpp中的自定義巨集,那麼定義了這個巨集的話表示這是一段cpp的程式碼,也就是說,上面的程式碼的含義是:如果這是一段cpp的程式碼,那麼加入extern "C"{和}處理其中的程式碼。
要明白為何使用extern "C",還得從cpp中對函式的過載處理開始說起。在c++中,為了支援過載機制,在編譯生成的彙編碼中,要對函式的名字進行一些處理,加入比如函式的返回型別等等.而在C中,只是簡單的函式名字而已,不會加入其他的資訊.也就是說:C++和C對產生的函式名字的處理是不一樣的.
比如下面的一段簡單的函式,我們看看加入和不加入extern "C"產生的彙編程式碼都有哪些變化:
- int f(void)
- {
- return 1;
- }
- .file "test.cxx"
- .text
- .align 2
- .globl _f
- .def _f; .scl 2; .type 32; .endef
- _f:
- pushl %ebp
- movl %esp, %ebp
- movl $1, %eax
- popl %ebp
- ret
- .file "test.cxx"
- .text
- .align 2
- .globl __Z1fv
- .def __Z1fv; .scl 2; .type 32; .endef
- __Z1fv:
- pushl %ebp
- movl %esp, %ebp
- movl $1, %eax
- popl %ebp
- ret
明白了加入與不加入extern "C"之後對函式名稱產生的影響,我們繼續我們的討論:為什麼需要使用extern "C"呢?C++之父在設計C++之時,考慮到當時已經存在了大量的C程式碼,為了支援原來的C程式碼和已經寫好C庫,需要在C++中儘可能的支援C,而extern "C"就是其中的一個策略。
試想這樣的情況:一個庫檔案已經用C寫好了而且執行得很良好,這個時候我們需要使用這個庫檔案,但是我們需要使用C++來寫這個新的程式碼。如果這個程式碼使用的是C++的方式連結這個C庫檔案的話,那麼就會出現連結錯誤.我們來看一段程式碼:首先,我們使用C的處理方式來寫一個函式,也就是說假設這個函式當時是用C寫成的:
- //f1.c
- extern "C"
- {
- void f1()
- {
- return;
- }
- }
- // test.cxx
- //這個extern表示f1函式在別的地方定義,這樣可以通過
- //編譯,但是連結的時候還是需要
- //連結上原來的庫檔案.
- extern void f1();
- int main()
- {
- f1();
- return 0;
- }
- test.o(.text + 0x1f):test.cxx: undefine reference to 'f1()'
因此,為了在C++程式碼中呼叫用C寫成的庫檔案,就需要用extern "C"來告訴編譯器:這是一個用C寫成的庫檔案,請用C的方式來連結它們。
比如,現在我們有了一個C庫檔案,它的標頭檔案是f.h,產生的lib檔案是f.lib,那麼我們如果要在C++中使用這個庫檔案,我們需要這樣寫:
- extern "C"
- {
- #include "f.h"
- }
- extern "C"
- {
- extern void f1();
- }
- int main()
- {
- f1();
- return 0;
- }
重新編譯並且連結就可以過去了.
總結
C和C++對函式的處理方式是不同的.extern "C"是使C++能夠呼叫C寫作的庫檔案的一個手段,如果要對編譯器提示使用C的方式來處理函式的話,那麼就要使用extern "C"來說明。