C調用C++庫和C++調用C庫的方法

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動態庫的兩種方式》。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章