C++調用C的靜態庫/動態庫
C++調用C的函數比較簡單,直接使用extern "C" {}
告訴編譯器用C的規則去調用C函數就可以了。
CAdd.h
int cadd(int x, int y);
CAdd.c
#include "CAdd.h"
#include <stdio.h>
int cadd(int x, int y) {
printf("from C function.\n");
return (x + y);
}
編譯libCAdd.a
gcc -c CAdd.c # 生成CAdd.o
ar -r libCAdd.a CAdd.o # 歸檔生成libCAdd.a
編譯動態庫 libCAdd.so
gcc -shared -o libCAdd.so CAdd.c
cppmain.cpp
#include <stdio.h>
extern "C" {
#include "CAdd.h"
}
int main()
{
int sum = cadd(1, 2);
printf("1+2 = %d\n", sum);
return 0;
}
編譯main
-l
指定庫名稱,優先鏈接so動態庫,沒有動態庫再鏈接.a靜態庫。
g++ -o cppmain cppmain.cpp -L. -lCAdd
運行
如果鏈接的是靜態庫就可以直接運行了,如果鏈接的是動態庫可能會提示
./cppmain: error while loading shared libraries: libCAdd.so: cannot open shared object file: No such file or directory
,是因爲Linux系統程序和Windows不一樣,Linux系統只會從系統環境變量指定的路徑加載動態庫,可以把生成的動態庫放到系統目錄,或者執行export LD_LIBRARY_PATH=./
設置當前路徑爲系統鏈接庫目錄就可以了。
註釋
這裏是在include頭文件的外面包裹了extern "C" { }
,是告訴編譯器以C語言的命名方式去加載這個符號。還有一種比較常見的方式是在頭文件中進行編譯聲明,如下所示,這樣的話,無論C還是C++直接正常include
就可以使用了。
CAdd.h
#ifdef __cplusplus
extern "C" {
#endif
int cadd(int x, int y);
#ifdef __cplusplus
}
#endif
C調用C++的靜態庫
C語言沒法直接調用C++的函數,但可以使用包裹函數來實現。C++文件.cpp
中可以調用C和C++的函數,但是C代碼.c
只能調用C的函數,所以可以用包裹函數去包裹C++函數,然後把這個包裹函數以C的規則進行編譯,這樣C就可以調用這個包裹函數了。
CppAdd.h
int cppadd(int x, int y);
CppAdd.cpp
#include "CppAdd.h"
#include <stdio.h>
int cppadd(int x, int y) {
printf("from C++ function.\n");
return (x + y);
}
編譯靜態庫 libCppAdd.a
g++ -c CppAdd.cpp
ar -r libCppAdd.a CppAdd.o
CppAddWrapper.h
#ifdef __cplusplus
extern "C" {
#endif
int cppaddwrapper(int x, int y);
#ifdef __cplusplus
}
#endif
CppAddWrapper.cpp
#include "CppAddWrapper.h"
#include <stdio.h>
#include "CppAdd.h"
int cppaddwrapper(int x, int y) {
printf("from wrapper.\n");
int sum = cppadd(x, y);
return sum;
}
編譯wrapper靜態庫 libCppAddWrapper.a
g++ -c CppAddWrapper.cpp
ar -r libCppAddWrapper.a CppAddWrapper.o
main.c
#include "CppAddWrapper.h"
#include <stdio.h>
int main()
{
int sum = cppaddwrapper(1, 2);
printf("1+2 = %d\n", sum);
return 0;
}
編譯main,同時指定libCppAdd.a 和 libCppAddWrapper.a。
gcc -o main main.c -L. -lCppAddWrapper -lCppAdd
或者把libCppAdd.a合併到libCppAddWrapper.a中
ar -x libCppAdd.a # 提取CppAdd.o
ar -x libCppAddWrapper.a # 提取CppAddWrapper.o
ar -r libCppAddWrapper.a CppAdd.o CppAddWrapper.o # 打包libCppAddWrapper.a
gcc -o main main.c -L. -lCppAddWrapper # 只需要連接libCppAddWrapper.a即可
如果是C調用C++的so動態庫的話,類似於調用靜態庫的方法應該也是有效的,太麻煩我沒試過。
總結
C/C++函數符號的區別
C++可以兼容C的語法,C/C++主要的區別是編譯函數符號規則不一樣,C語言代碼編譯後的函數名還是原來函數名,C++代碼編譯後的函數名帶有參數信息。
做個測試來檢驗一下。一個簡單的函數,分別用C和C++進行編譯。
hello1.c
int test(int a, char* b){
return a;
}
hello2.cpp
int test(int a, char* b){
return a;
}
編譯
gcc -c hello1.c # 生成hello1.o
g++ -c hello1.cpp # 生成hello2.o
查看符號表
$ nm hello1.o
0000000000000000 T test
$ nm hello2.o
0000000000000000 T _Z4testiPc
從上面信息可以看出,C語言編譯後的函數符號還是原函數名,而C++編譯後的函數符號由test變成了_Z4testiPc,從這個符號名字可以看出test前面有個數字4應該是函數名長度,test後面i Pc應該就是函數的參數簽名。C++之所以這樣規定編譯後的函數符號是因爲對面對象的C++具有函數重載功能,以此來區分不同的函數。
.so動態庫、.a靜態庫和.o中間文件的關係
程序的運行都要經過編譯和鏈接兩個步驟。假如有文件add.c
,可以使用命令gcc -c add.c
進行編譯,生成add.o
中間文件,使用命令ar -r libadd.a add.o
可以生成libadd.a
靜態庫文件。靜態庫文件其實就是對.o中間文件進行的封裝,使用nm libadd.a
命令可以查看其中封裝的中間文件以及函數符號。
鏈接靜態庫就是鏈接靜態庫中的.o文件,這和直接編譯多個文件再鏈接成可執行文件一樣。
動態鏈接庫是程序執行的時候直接調用的“插件”,使用命令gcc -shared -o libadd.so add.c
生成so動態庫。動態庫鏈接的時候可以像靜態庫一樣鏈接,告訴編譯器函數的定義在這個靜態庫中(避免找不到函數定義的錯誤),只是不把這個so打包到可執行文件中。如果沒有頭文件的話,可以使用dlopen/dlsum函數手動去加載相應的動態庫。詳細做法參考上一篇文章《C語言調用so動態庫的兩種方式》。